Fork me on GitHub
#malli
<
2022-01-07
>
Panel13:01:19

Trying to understand why transformers won't compose, in this ex, (mt/transformers {:decoders ...}) is compiled but not run when the second transformer (mt/key-transformer ...) is present. If I remove (mt/key-transformer ...) then (mt/transformers {:decoders ...}) run just fine. I'm guessing this is related to interceptor :before and :after clashing for some reason.

(ma/decode
 [:map ["ContactMethodCde"
        [:enum {:lkp {"pcme" "Email"
                    "pcmp" "Phone"
                    "pcmm" "Mail"}}
         "Mail" "Phone" "Email"]]]
 {"ContactMethodCde" "pcme"}
 (mt/transformer
  {:decoders {:enum {:compile (fn [schema _]
                              (let [m (:lkp (ma/properties schema))]
                                (fn [x]
                                  (prn x)
                                  (m x))))}}}
  (mt/key-transformer
   {:decode {"ContactMethodCde" :contact-method}})))
This work, notice the :leave interceptor on :map decoders.
(ma/decode
 [:map ["ContactMethodCde"
        [:enum {:lkp {"pcme" "Email"
                      "pcmp" "Phone"
                      "pcmm" "Mail"}}
         "Mail" "Phone" "Email"]]]
 {"ContactMethodCde" "pcme"}
 (mt/transformer
  {:decoders
   {:enum {:compile (fn [schema _]
                      (let [m (:lkp (ma/properties schema))]
                        {:enter(fn [x]
                                  (prn x)
                                  (m x))}))}
    :map {:leave (mt/-transform-map-keys {"ContactMethodCde" :contact-method})}}}))
It wont work when the decoder is set to :enter :map {:enter ...}

Grant Horner15:01:35

Is there an equivalent to spec-alpha2's select in malli? i.e., it takes a :map schema and makes everything optional other than the specified keys?

ikitommi20:01:20

something like this?

(require '[malli.core :as m])
(require '[malli.util :as mu])

(defn select [schema keys]
  (-> schema
      (mu/optional-keys)
      (mu/required-keys keys)))

(select
 [:schema {:registry {::name :string
                      ::title [:enum "boss" "not-boss"]
                      ::age :int
                      ::skills [:set [:enum "clj" "cljs"]]}}
  [:map ::name ::title ::age ::skills]]
 #{::name ::age})
;[:map
; [:user/name :user/name]
; [:user/title {:optional true} :user/title]
; [:user/age :user/age]
; [:user/skills {:optional true} :user/skills]]

ikitommi20:01:57

should be easy to make it recursive, I think there is an issue to add such a tool to malli.util

Grant Horner22:01:19

@U055NJ5CC that's definitely simpler than my solution… thanks!

Noah Bogart21:07:52

what about this?

(defn select
  [schema ks]
  (let [schema (m/schema schema)
        plain-keys (filter keyword? ks)
        map-keys (->> ks
                      (filter map?)
                      (apply merge)
                      (not-empty))
        req-keys (vec (concat plain-keys (keys map-keys)))
        changed-schema (-> (mu/optional-keys schema)
                           (mu/required-keys req-keys))]
    (if-let [paths map-keys]
      (reduce (fn [schema [path v]]
                (assert (vector? v) "Nested map vals must be vectors")
                (mu/update schema path select v))
              changed-schema
              paths)
      changed-schema)))

Noah Bogart21:07:04

works like this:

(select
  (m/schema
    [:map
     [:name string?]
     [:title [:enum "boss" "not-boss"]]
     [:skills [:set [:enum "clj" "cljs"]]]
     [:base [:map
             [:this-one [:map
                         [:one uuid?]
                         [:two int?]]]
             [:not-this-one string?]]]])
        [:name {:base [{:this-one [:two]}]}])
; [:map
;  [:name string?]
;  [:title {:optional true} [:enum "boss" "not-boss"]]
;  [:skills {:optional true} [:set [:enum "clj" "cljs"]]]
;  [:base [:map
;          [:this-one [:map
;                      [:one {:optional true} uuid?]
;                      [:two int?]]]
;          [:not-this-one {:optional true} string?]]]]

ikitommi20:01:20

something like this?

(require '[malli.core :as m])
(require '[malli.util :as mu])

(defn select [schema keys]
  (-> schema
      (mu/optional-keys)
      (mu/required-keys keys)))

(select
 [:schema {:registry {::name :string
                      ::title [:enum "boss" "not-boss"]
                      ::age :int
                      ::skills [:set [:enum "clj" "cljs"]]}}
  [:map ::name ::title ::age ::skills]]
 #{::name ::age})
;[:map
; [:user/name :user/name]
; [:user/title {:optional true} :user/title]
; [:user/age :user/age]
; [:user/skills {:optional true} :user/skills]]

Noah Bogart21:07:52

what about this?

(defn select
  [schema ks]
  (let [schema (m/schema schema)
        plain-keys (filter keyword? ks)
        map-keys (->> ks
                      (filter map?)
                      (apply merge)
                      (not-empty))
        req-keys (vec (concat plain-keys (keys map-keys)))
        changed-schema (-> (mu/optional-keys schema)
                           (mu/required-keys req-keys))]
    (if-let [paths map-keys]
      (reduce (fn [schema [path v]]
                (assert (vector? v) "Nested map vals must be vectors")
                (mu/update schema path select v))
              changed-schema
              paths)
      changed-schema)))