Fork me on GitHub
#malli
<
2022-10-12
>
timothypratley04:10:47

Hello ๐Ÿ‘‹ To include a literal, is it best to use :fn ? In these examples I want to limit the matches to a specific value. This seems to work:

((ma/parser [:fn #{1}]) 1)
;=> 1
((ma/parser [:* [:cat [:fn #{1}] [:fn #{2}]]])
 [1 2 1 2 1 2])
;=> [[1 2] [1 2] [1 2]]
Does this seem like the correct approach?

Chris Oโ€™Donnell12:10:21

I would probably use [:= 1] personally, but your approach seems fine, too.

๐Ÿ˜Ž 1
timothypratley20:10:53

Oh great thank you, I didn't see := that's what I was hoping for

๐Ÿ‘ 1
Ben Sless05:10:15

fn spec is best avoided IMO, it makes it hard to reason about specs

Stig Brautaset14:10:21

Can I instrument protocol methods with Malli? We use protocols (implemented by records) quite a lot. With Spec we add an indirection via a regular function to have something to hang Clojure Spec specs off of. Is that what Iโ€™d do with Malli too?

Prashant18:10:20

Hi, I was curious if anyone has any pointers/implementation on validating EntityMap using Malli . These are returned by (datomic.api/entity db entity-id) . These EntityMap objects don't implement IPersistentMap so schema like [:map ...] fails with type mismatch. I would greatly appreciate the inputs.

dvingo02:10:28

You can create your own schema type using -simple-schema (here are some examples: https://github.com/metosin/malli/blob/1a9b3767f1d64d504663ca151363244db2635708/src/malli/core.cljc#L660) I'm not sure on the details but I think you can make your predicate function convert the entity to a hashmap and then leverage the existing :map schema type by having your custom schema type pass its arguments to the :map type

Prashant13:10:43

Thanks @U051V5LLP I will give it a shot.

Prashant14:10:26

I have reached till below and was wondering how to pass the arguments to the :map type.

(defn entity?
  [entity]
  (instance? datomic.query.EntityMap entity))


(defn -entitiy-map-schema
  []
  (-simple-schema
    {:type :entity-map
     :pred entity?     
     :type-properties {:decode/map #(if (entity? %)
                                      (into {} %)
                                      %)}}))
On a sidenote, since a instances of datomic.query.EntityMap may have nested datomic.query.EntityMap, I think using a ::ref would also be needed?

dvingo14:10:08

hmm, I think it might not be as simple as using -simple-schema you may have to copy the -map-schema implementation and delegate to that, here is a heavily hacked one I got working but gives the general idea:

dvingo14:10:27

(defn -entity-map-schema
  ([]
   (-entity-map-schema {:naked-keys true}))
  ([opts] ;; :naked-keys, :lazy
   ^{:type ::into-schema}
   (reify
     m/IntoSchema
     (-type [_] :entity-map)
     (-type-properties [_])
     (-properties-schema [_ _])
     (-children-schema [_ _])
     (-into-schema [parent {:keys [closed] :as properties} children options]
       (let [map-schema   (m/schema (into [:map] children))
             entry-parser (m/-create-entry-parser children opts options)
             cache        (m/-create-cache options)]
         ^{:type ::schema}
         (reify
           m/AST
           (m/-to-ast [this _] (m/-to-ast map-schema _))
           m/Schema
           (-validator [this]
             (let [keyset     (m/-entry-keyset (m/-entry-parser this))
                   _          (println "2 " (m/children map-schema))
                   validators (map
                                (fn [[key {:keys [optional]} value]]
                                    (let [valid?  (m/-validator value)
                                          default (boolean optional)]
                                      (fn [m] (if-let [map-entry (find m key)]
                                                (valid? (val map-entry))
                                                default))))
                                (m/children map-schema))

                   validate   (apply every-pred validators)]

               ;; THIS LINE IS THE SIGNIFICANT CHANGE:
               ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
               (fn [m] (and
                         (instance? datomic.query.EntityMap m)
                          (validate (into {} m))))))

           (-explainer [this path] (m/-explainer map-schema path))
           (-parser [this] (m/-parser map-schema))
           (-unparser [this] (m/-unparser map-schema))
           (-transformer [this transformer method options] (m/-transformer map-schema transformer method options))
           (-walk [this walker path options] (m/-walk-entries map-schema walker path options))
           (-properties [_] properties)
           (-options [_] options)
           (-children [_] (m/-entry-children entry-parser))
           (-parent [_] parent)
           (-form [_] (m/-form map-schema))
           m/EntrySchema
           (-entries [_] (m/-entry-entries entry-parser))
           (-entry-parser [_] entry-parser)
           m/Cached
           (-cache [_] cache)
           m/LensSchema
           (-keep [_] true)
           (-get [this key default] (m/-get-entries this key default))
           (-set [this key value] (m/-set-entries this key value))))))))

dvingo14:10:13

the main thing is when malli creates the schema (using -into-schema you take the provided children and create an underlying :map schema

map-schema   (m/schema (into [:map] children))

dvingo14:10:29

then fill in all the protocol methods for IntoSchema using that

Prashant14:10:10

Thanks a ton!!! this is super helpful.

dvingo14:10:30

there may be a much simpler way to do this, I'm not an expert on this, just figuring things out by reading the source code of malli.core

dvingo15:10:07

and you can try it with:

(m/validate
  (m/schema [:entity-map [:x :int]] 
    {:registry (assoc (m/default-schemas) :entity-map (-entity-map-schema))})
  (d/entity db [:some/id-prop 5]))

Prashant15:10:28

This gives a very good head start. I was also thinking on the same lines of copying map schema and hacking through it.

dvingo15:10:54

sure thing! I'm still learning myself, so would be curious what you come up with ๐Ÿ™‚

๐Ÿ‘ 1
ikitommi15:10:19

Would it help if the malli.core/-map-schema took an extra option for the predicate? e.g. on could just do:

(def EntityMap (m/-map-schema {:pred entity?}))

(m/validate EntityMap ...)

๐Ÿ‘ 1
ikitommi15:10:42

Malli is intended to be extendable, so all IntoSchemas are created using functions and itโ€™s a easy & non-breaking change to add new options ๐Ÿ™‚

ikitommi15:10:03

(also, effects bundle-size on cljs, non-used schemas can be DCEd)

ikitommi15:10:40

anyway, PR welcome on adding the :pred for the m/-map-schema

๐Ÿ‘ 1
Prashant14:10:23

Thanks for the suggestion Tommi. I will definitely look into implementing your suggestion and when satisfied, raise a PR ๐Ÿ™‚