Fork me on GitHub
#malli
<
2023-09-11
>
Samuel Ludwig18:09:36

how should I write a m/=> function schema for a fn that takes in an open-ended option-map (where I need to ensure types if certain keys are provided)? for example:

(def Entity
  (m/schema
    [:map
     [:id uuid?]
     [:attributes map?]]))

(m/=> create-new [:=> [:cat ___?] Entity])
(defn create-new [& {:as kvs}]
  (merge
   {:id (random-uuid)
    :attributes {}}
   kvs))
which might be called as
(create-new 
  :attributes {:one 2 :three [4 5]} 
  :six 7)
not seeing any examples in the docs afaict (but I may have missed them)

dvingo21:09:02

I'm not sure of a way to describe that either, but maybe a small addition to malli.core/-instrument can help: In my fork I added the ability to do this:

(def Entity (m/schema [:map [:id uuid?] [:attributes map?]]))
(def Args (m/schema [:map
                     [:id {:optional true} uuid?]
                     [:other {:optional true} :string]
                     [:attributes {:optional true} map?]]))

(defn create-new
  {:malli/schema [:=> [:cat Args] Entity]
   :malli/xform-args (fn [& x] [(apply hash-map x)])}
  [& {:as kvs}]
  (merge
    {:id         (random-uuid)
     :attributes {}}
    kvs))
;; call with 
(d/pre (pr-str (create-new :id "(random-uuid)")
https://github.com/dvingo/malli/blob/5e1ce89dbfa9a6fa55724e72a8aeffcf0479cbca/src/malli/core.cljc#L2564 which gives this error in cljs:

dvingo21:09:55

This was needed to get integration with the helix library where the function will receive a js object but by using a macro it appears to be a hashmap. So I needed a way to transform the arguments so malli would validate it correctly

dvingo22:09:28

not sure if this would be accepted upstream, but you can copy dev/start! to use this version of instrument in your project

dvingo22:09:51

maybe => can be updated to accept optional data as well so that version would support this transform

ikitommi11:09:14

two things in this thread: 1. there is no inbuilt schema to support kw-args, but you can describe them with :alt, example below. Issue welcome if you want a :kwargs utility schema for this 2. xform-args - looks interesting, is this just for validating or also as a “transform args behind the scenes” thing?

ikitommi11:09:05

so, here it would be:

[:altn
 [:map [:map
        [:id uuid?]
        [:attributes map?]]]
 [:args [:*
         [:alt
          [:cat [:= :id] uuid?]
          [:cat [:= :attributes] map?]]]]]

ikitommi11:09:24

or just:

[:alt
 [:map
  [:id uuid?]
  [:attributes map?]]
 [:*
  [:alt
   [:cat [:= :id] uuid?]
   [:cat [:= :attributes] map?]]]]

ikitommi11:09:17

but, there could be something like:

[:sequential-map
 [:id uuid?]
 [:attributes map?]]
… that would behave like it.

ikitommi12:09:04

and because it’s all data, this should work too:

(defn kwargs [m]
  [:altn
   ["map" (m/form m)]
   ["args" [:* (into [:alt] (reduce (fn [acc [k _ v]] (conj acc [:cat [:= k] v])) [] (m/children m)))]]])

(kwargs
 [:map
  [:id uuid?]
  [:attributes map?]])
;[:altn
; ["map"
;  [:map 
;   [:id uuid?]
;   [:attributes map?]]]
; ["args" 
;  [:* 
;   [:alt 
;    [:cat [:= :id] uuid?] 
;    [:cat [:= :attributes] map?]]]]]

❤️ 1
🚀 1
dvingo01:09:54

thanks for the pointers Tommi! good to know about :alt and :altn For the xform-args - it's intended to only be used for validations, the function will get the original args unprocessed It came from a use-case where helix, the react library, emits a function for you via a macro that will transform a JS object to hashmap, but I wanted to add instrumentation to by describing the args with a :map schema. This xform-args was one solution to get it working without needing to add more macro layers. But it seems like it could be useful in other cases where a transformation can simplify the arguments schema validation

ikitommi12:09:04

and because it’s all data, this should work too:

(defn kwargs [m]
  [:altn
   ["map" (m/form m)]
   ["args" [:* (into [:alt] (reduce (fn [acc [k _ v]] (conj acc [:cat [:= k] v])) [] (m/children m)))]]])

(kwargs
 [:map
  [:id uuid?]
  [:attributes map?]])
;[:altn
; ["map"
;  [:map 
;   [:id uuid?]
;   [:attributes map?]]]
; ["args" 
;  [:* 
;   [:alt 
;    [:cat [:= :id] uuid?] 
;    [:cat [:= :attributes] map?]]]]]

❤️ 1
🚀 1