malli

https://github.com/metosin/malli :malli:
yuhan 2021-03-18T02:57:55.019800Z

Hi, I'm just working through the Readme tutorial on the latest 0.3.0 and found that this example failed:

(m/validate
  [:map
   ["status" [:enum "ok"]]
   [1 any?]
   [nil any?]
   [::a string?]]
  {"status" "ok"
   1 'number
   nil :yay
   ::a "properly awesome"})
; => true
Instead I get an exception:
1. Unhandled clojure.lang.ExceptionInfo
   :malli.core/naked-keys-not-supported nil
   {:type :malli.core/naked-keys-not-supported, :data nil}

yuhan 2021-03-18T03:00:46.020600Z

Is this a regression or API change? I couldn't find references to "naked keys" apart from internal impl code

ikitommi 2021-03-18T05:29:23.021500Z

@qythium it was a regression, fixed in master

๐Ÿ‘Œ 1
Hankstenberg 2021-03-18T08:19:27.022400Z

Is there a way to filter data by a schema? So if I have data of the form {:a 1 :b 2} and a schema of the form [:map [:a int?]] can I "apply" the schema to the data in order to get {:a 1}? All I can think of right now is to use m/explain then parse the errors.

ikitommi 2021-03-18T09:01:27.023300Z

@raymcdermott would this be ok:

(def Org-Ref
  [:map {:title "Organisation name"}
   [:ref {:swagger/description "Reference to the organisation"
          :swagger/example "Acme floor polish, Houston TX"} :string]
   [:kikka [:string {:swagger {:title "kukka"}}]]])

(defn remove-swagger-keys [p]
  (not-empty (apply dissoc p (into #{:swagger} (->> p (keys) (filter (comp #{:swagger} keyword namespace)))))))

(defn walk-properties [schema f]
  (m/walk
    schema
    (fn [s _ c _]
      (m/into-schema
        (m/-parent s)
        (f (m/-properties s))
        (cond->> c (m/entries s) (map (fn [[k p s]] [k (f p) (first (m/children s))])))
        (m/options s)))
    {::m/walk-entry-vals true}))

(walk-properties Org-Ref remove-swagger-keys)
;[:map {:title "Organisation name"} 
; [:ref :string] 
; [:kikka :string]]

ikitommi 2021-03-18T09:02:02.024Z

e.g. walk the entrys, un-walk on the way back. apply f on all properties (entrys & schemas)

ikitommi 2021-03-18T09:03:56.025Z

@roseneck you can transform the value using strip-extra-keys-transformer:

(m/decode [:map [:a int?]] {:a 1, :b 2} (mt/strip-extra-keys-transformer))
; => {:a 1}

Hankstenberg 2021-03-18T09:09:33.025500Z

@ikitommi perfect, thank you very much!

raymcdermott 2021-03-18T09:50:02.026100Z

yes @ikitommi that's very elegant

robert-stuttaford 2021-03-18T10:05:43.027500Z

is it possible to introspect the malli schema from inside an :error/fn fn ? i.e. i want to (first (m/children schema)) so i can print out the enum values in the error message

ikitommi 2021-03-18T10:34:30.028800Z

@robert-stuttaford sure, the fn takes the explain error map as argument, it has the :schema key.

ikitommi 2021-03-18T10:35:06.029700Z

there are lot of examples in malli.error

euccastro 2021-03-18T10:52:45.031900Z

how do I transform the keys of a schema? e.g., I have [:map [:a-k string?] [:b-k string?]] and I want to derive a schema that is the same but with snake-case keys: [:map [:a_k string?] [:b_k string?]]

robert-stuttaford 2021-03-18T11:02:27.032Z

when i try this, i get m/children isn't a thing, because it's SCI that's running this code

robert-stuttaford 2021-03-18T11:02:48.032200Z

(thank you for your quick response)

robert-stuttaford 2021-03-18T11:05:08.032400Z

Execution error (ExceptionInfo) at sci.impl.utils/throw-error-with-location (utils.cljc:51).
Could not resolve symbol: m/children

robert-stuttaford 2021-03-18T11:05:21.032600Z

is there a trick to get it to see malli?

borkdude 2021-03-18T11:08:30.032800Z

@robert-stuttaford just out of curiosity: what is your use case for malli + SCI?

๐Ÿ‘‚ 1
borkdude 2021-03-18T11:08:44.033100Z

malli could make this namespace available inside of sci

ikitommi 2021-03-18T11:08:45.033300Z

there is ::m/sci-options to override the bindings. The default bindings are:

(defn -default-sci-options []
  {:preset :termination-safe
   :bindings {'m/properties properties
              'm/type type
              'm/children children
              'm/entries entries}})
have the sci-options changed? donโ€™t seem to work anymre

ikitommi 2021-03-18T11:09:11.033500Z

:termination-safe is removed at least

borkdude 2021-03-18T11:09:28.033700Z

the options have not been changed, but :bindings are only valid within the user namespace, always have been. it's better to use explicit :namespaces

robert-stuttaford 2021-03-18T11:09:42.034Z

honestly i'm using sci because malli is

borkdude 2021-03-18T11:09:50.034200Z

yes :termination-safe has been removed for a while already, also documented in release notes

robert-stuttaford 2021-03-18T11:10:10.034400Z

all i'm doing is writing malli specs at the repl with :error/fn and i ran into an error 'sci not available', so i put it on the CP and onward i went

ikitommi 2021-03-18T11:10:20.034600Z

@robert-stuttaford if you donโ€™t need the seriaization thing, just pass a real function.

robert-stuttaford 2021-03-18T11:10:41.034800Z

oh man. the fn is quoted. shit. sorry for the noise, fellas

ikitommi 2021-03-18T11:10:50.035Z

๐Ÿ™‚

borkdude 2021-03-18T11:10:54.035200Z

that's what I thought :) maybe the error message should be : use sci for serialized schemas

borkdude 2021-03-18T11:10:56.035400Z

or something

ikitommi 2021-03-18T11:11:47.035600Z

all properties which have functions as values use the malli.eval, which uses sci as default for quoted code.

ikitommi 2021-03-18T11:12:08.035800Z

e.g.`:gen/fmap '(partial str "kikka_")`

ikitommi 2021-03-18T11:12:42.036Z

but, what is the right way to bind those m/children into sci via options?

borkdude 2021-03-18T11:13:18.036200Z

{:namespaces {'malli.core {'children m/children}}}

ikitommi 2021-03-18T11:15:09.036400Z

doesnโ€™t work either.

ikitommi 2021-03-18T11:15:32.036600Z

(defn -default-sci-options []
  {:namespaces {'malli.core {'properties properties
                             'type type
                             'children children
                             'entries entries}}})

borkdude 2021-03-18T11:15:37.036800Z

doesn't work = which error?

ikitommi 2021-03-18T11:15:54.037Z

Execution error (ExceptionInfo) at sci.impl.utils/throw-error-with-location (utils.cljc:50).
Could not resolve symbol: malli.core/chidren [at line 1, column 10]

ikitommi 2021-03-18T11:16:01.037200Z

(defn evaluator [options fail!]
  (let [eval-string* (dynaload/dynaload 'sci.core/eval-string* {:default nil})
        init (dynaload/dynaload 'sci.core/init {:default nil})
        fork (dynaload/dynaload 'sci.core/fork {:default nil})]
    (fn [] (if (and @eval-string* @init @fork)
             (let [ctx (init options)]
               (fn eval [s] (eval-string* (fork ctx) (str s))))
             fail!))))

borkdude 2021-03-18T11:16:10.037400Z

"malli.core/chidren" <- typo?

ikitommi 2021-03-18T11:16:35.037600Z

:face_palm:

ikitommi 2021-03-18T11:16:49.037800Z

thanks, works like a charm

ikitommi 2021-03-18T11:17:20.038Z

what about binding m -> malli.core for not breaking things?

ikitommi 2021-03-18T11:21:41.039100Z

@euccastro try m/walk with m/schema-walker check that the schema is a :map and recreate the children.

euccastro 2021-03-18T11:25:50.039300Z

thank you!

borkdude 2021-03-18T11:29:32.039400Z

you can manually insert a (require '[malli.core :as m]) to ensure this works

borkdude 2021-03-18T11:30:02.039600Z

or (alias 'm 'malli.core)

ikitommi 2021-03-18T11:33:26.039800Z

like:

(defn evaluator [options fail!]
  (let [eval-string* (dynaload/dynaload 'sci.core/eval-string* {:default nil})
        init (dynaload/dynaload 'sci.core/init {:default nil})
        fork (dynaload/dynaload 'sci.core/fork {:default nil})]
    (fn [] (if (and @eval-string* @init @fork)
             (let [ctx (init options)]
               (eval-string* ctx "(alias 'm 'malli.core)")
               (fn eval [s] (eval-string* (fork ctx) (str s))))
             fail!))))

ikitommi 2021-03-18T11:33:49.040Z

seems to work

๐Ÿ‘ 1
ikitommi 2021-03-18T11:34:48.040300Z

((m/eval 'm/type) :int)
; =&gt; :int

ikitommi 2021-03-18T11:34:52.040500Z

๐Ÿ™‡

robert-stuttaford 2021-03-18T11:47:20.040900Z

is the best way to specify a literal value to use a single-item enum?

robert-stuttaford 2021-03-22T07:03:20.138800Z

nice man

2021-03-18T11:49:18.041Z

[:= value]

robert-stuttaford 2021-03-18T11:50:36.041200Z

noice!

borkdude 2021-03-18T12:15:52.041400Z

Tried to use this to build a core.match like this ;) https://gist.github.com/borkdude/26906ee15585ed5e1b7a8eda4cc1ee18

danielneal 2021-03-18T16:35:05.042Z

dumb question, how do you get a schema from the registry by its key

danielneal 2021-03-18T16:35:15.042200Z

I was thinking (malli/-schema malli/default-registry ::sq/sqid)

danielneal 2021-03-18T16:35:19.042400Z

but -schema is private

emccue 2021-03-18T16:37:10.042700Z

[:map
    [:field-a string?]
    [:field-b [:one-of 
               [:map [:status [:= :not-asked]]]
               [:map [:status [:= :loading]]]
               [:map [:status [:= :failed]]]
               [:map [:status [:= :success]
                      :value  [:vector [:map [:id int?]]]]]]]
    [:field-c [:one-of
                [:map [:status [:= :not-asked]]]
                [:map [:status [:= :loading]]]
                [:map [:status [:= :failed]]]
                [:map [:status [:= :success]
                       :value  [:vector [:map [:id int?]]]]]]]
    
    [:field-d [:set [:map [:id int?]]]]]

emccue 2021-03-18T16:37:24.043100Z

what would the idiomatic way to represent this be?

emccue 2021-03-18T16:39:17.043500Z

trying on the playground it doesn't say its wrong

emccue 2021-03-18T16:39:21.043600Z

emccue 2021-03-18T16:39:41.044100Z

but it also doesn't produce any sample values

ikitommi 2021-03-18T16:40:30.044900Z

@emccue there is no one-of, you can use :or

emccue 2021-03-18T16:41:42.045300Z

oh.

emccue 2021-03-18T16:41:46.045600Z

yep that did it

๐Ÿ‘ 1
ikitommi 2021-03-18T16:42:42.047200Z

could also be a :multi dispatching on :type .

ikitommi 2021-03-18T16:44:52.049200Z

@danieleneal try (m/deref (m/schema ::sq/said))

emccue 2021-03-18T16:45:44.050600Z

What is the benefit of multi schemas over explicit listing like that?

ikitommi 2021-03-18T16:45:46.050800Z

(m/deref ::sq/said) might work too.

ikitommi 2021-03-18T16:47:43.053300Z

might not be big difference, but performance. dispatch does one lookup to find the correct schema, :or does linear scan over all.

emccue 2021-03-18T16:49:38.054300Z

I think last question for now - what would be the best way to reuse a structure like this

emccue 2021-03-18T16:49:54.054700Z

just a function that takes in the success value schema and returns the whole thing?

danielneal 2021-03-18T16:56:41.055100Z

@ikitommi, (m/deref ::sq/sqid) works thanks :thumbsup:

danielneal 2021-03-18T17:27:14.056400Z

is there a way of getting the schema walker to deref while walking? I've got a schema which is like [:map [:some-key :some-schema] [:another-key :another-schema]] where :some-schema and :another-schema are in the registry. I want to transform all the keys to snake case, but the schema walker doesn't descend into schema references.

ikitommi 2021-03-18T17:35:21.057100Z

@danieleneal see: > e.g. ::m/walk-refs & ::m/walk-schema-refs & ::m/walk-entry-vals.

ikitommi 2021-03-18T17:36:00.058200Z

walking respects those options, can't recall what does what. Please try, documentation PR welcome

ikitommi 2021-03-18T17:36:59.059100Z

I recall those are recursion safe

ikitommi 2021-03-18T17:37:31.059900Z

e.g. stop of first deref of already walked reference

danielneal 2021-03-18T17:38:47.060100Z

oh cool

danielneal 2021-03-18T17:38:50.060300Z

thanks again!!!

ikitommi 2021-03-18T21:21:43.061200Z

@emccue one way to reuse is to use local registry:

[:map {:registry {:user/success [:map
                                 [:status [:= :success]]
                                 [:value [:vector [:map [:id int?]]]]]
                  :user/default [:map 
                                 [:status [:enum :not-asked :loading :failed]]]
                  :user/field [:multi {:dispatch :status}
                               [:success :user/success]
                               [:malli.core/default :user/default]]}}
 [:field-a string?]
 [:field-b :user/field]
 [:field-c :user/field]
 [:field-d [:set [:map [:id int?]]]]]

ikitommi 2021-03-18T21:22:47.062300Z

could be of course global registry too.

emccue 2021-03-18T21:23:08.062800Z

I mean like, this is analagous to a typed enum from other langs

emccue 2021-03-18T21:23:16.063100Z

so i would in my brain go

emccue 2021-03-18T21:23:34.063600Z

(remote-data [:map [:id int?]])

emccue 2021-03-18T21:23:39.063800Z

and reuse the pattern

ikitommi 2021-03-18T21:24:37.064400Z

oh, sure. itโ€™s just data, so a function liike that is the way to do it.

emccue 2021-03-18T21:29:35.065400Z

but by the same token, if i have a spec like this in a namespace

emccue 2021-03-18T21:29:50.065800Z

(def user [:map [:name string?]])

emccue 2021-03-18T21:29:58.066100Z

I shouldn't use it like this

emccue 2021-03-18T21:30:14.066500Z

(def school [:vector other.ns/user])

emccue 2021-03-18T21:30:18.066700Z

right?

emccue 2021-03-18T21:30:43.067200Z

because then it will get "flattened" and the errors won't be as good

nilern 2021-03-18T21:32:27.067600Z

That's how we used to do it with Plumatic Schema

nilern 2021-03-18T21:35:59.070700Z

We wanted to enable that style too, you may give up some serialization benefits but get to use normal defs etc.

nilern 2021-03-18T21:40:42.071900Z

The registry names don't play any role in e.g. explainer actually

emccue 2021-03-18T21:49:22.072700Z

so then a local registry is basically equivalent to a let?

emccue 2021-03-18T21:49:36.073100Z

(but "runs" in the schema?)