Fork me on GitHub
#malli
<
2021-03-18
>
yuhan02:03:55

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}

yuhan03:03:46

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

ikitommi05:03:23

@qythium it was a regression, fixed in master

👌 3
Hankstenberg08:03:27

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.

ikitommi09:03:27

@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]]

ikitommi09:03:02

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

ikitommi09:03:56

@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}

Hankstenberg09:03:33

@ikitommi perfect, thank you very much!

genRaiy09:03:02

yes @ikitommi that's very elegant

robert-stuttaford10:03:43

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

ikitommi10:03:30

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

ikitommi10:03:06

there are lot of examples in malli.error

robert-stuttaford11:03:27

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

robert-stuttaford11:03:48

(thank you for your quick response)

robert-stuttaford11:03:08

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

robert-stuttaford11:03:21

is there a trick to get it to see malli?

borkdude11:03:30

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

👂 3
borkdude11:03:44

malli could make this namespace available inside of sci

ikitommi11:03:45

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

ikitommi11:03:11

:termination-safe is removed at least

borkdude11:03:28

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-stuttaford11:03:42

honestly i'm using sci because malli is

borkdude11:03:50

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

robert-stuttaford11:03:10

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

ikitommi11:03:20

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

robert-stuttaford11:03:41

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

borkdude11:03:54

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

borkdude11:03:56

or something

ikitommi11:03:47

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

ikitommi11:03:08

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

ikitommi11:03:42

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

borkdude11:03:18

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

ikitommi11:03:09

doesn’t work either.

ikitommi11:03:32

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

borkdude11:03:37

doesn't work = which error?

ikitommi11:03:54

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]

ikitommi11:03:01

(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!))))

borkdude11:03:10

"malli.core/chidren" <- typo?

ikitommi11:03:35

:face_palm:

ikitommi11:03:49

thanks, works like a charm

ikitommi11:03:20

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

borkdude11:03:32

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

borkdude11:03:02

or (alias 'm 'malli.core)

ikitommi11:03:26

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!))))

ikitommi11:03:49

seems to work

👍 3
ikitommi11:03:48

((m/eval 'm/type) :int)
; => :int

euccastro10:03:45

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?]]

ikitommi11:03:41

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

robert-stuttaford11:03:20

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

danielneal16:03:05

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

danielneal16:03:15

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

danielneal16:03:19

but -schema is private

emccue16:03:10

[: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?]]]]]

emccue16:03:24

what would the idiomatic way to represent this be?

emccue16:03:17

trying on the playground it doesn't say its wrong

emccue16:03:41

but it also doesn't produce any sample values

ikitommi16:03:30

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

emccue16:03:46

yep that did it

👍 3
ikitommi16:03:42

could also be a :multi dispatching on :type .

ikitommi16:03:52

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

emccue16:03:44

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

ikitommi16:03:46

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

ikitommi16:03:43

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

emccue16:03:38

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

emccue16:03:54

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

danielneal16:03:41

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

danielneal17:03:14

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.

ikitommi17:03:21

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

ikitommi17:03:00

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

ikitommi17:03:59

I recall those are recursion safe

ikitommi17:03:31

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

danielneal17:03:50

thanks again!!!

ikitommi21:03:43

@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?]]]]]

ikitommi21:03:47

could be of course global registry too.

emccue21:03:08

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

emccue21:03:16

so i would in my brain go

emccue21:03:34

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

emccue21:03:39

and reuse the pattern

ikitommi21:03:37

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

emccue21:03:35

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

emccue21:03:50

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

emccue21:03:58

I shouldn't use it like this

emccue21:03:14

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

emccue21:03:43

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

nilern21:03:27

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

nilern21:03:59

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

nilern21:03:42

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

emccue21:03:22

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

emccue21:03:36

(but "runs" in the schema?)