Fork me on GitHub
#malli
<
2020-10-15
>
ikitommi15:10:19

@raymcdermott noticed that none of the malli.util helpers deref the refernce schemas, wrote an https://github.com/metosin/malli/issues/281. Before that, to use registry references, one needs to deref those manually, one option being:

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

(mr/set-default-registry!
  (mr/composite-registry
    (m/default-schemas)
    {:asset/id string?
     :asset/status [:enum :initialized :accepted :active :revoked :failed]
     :asset/asset [:map
                   :asset/id
                   :asset/status
                   [:asset/asset {:optional true} [:ref :asset/asset]]]}))

(def org-asset
  (mu/merge
    (m/deref :asset/asset)
    [:map [:asset/org [:= "org"]]]))

org-asset
;[:map
; [:asset/id :asset/id]
; [:asset/status :asset/status]
; [:asset/asset {:optional true} [:ref :asset/asset]]
; [:asset/org [:= "org"]]]

(def user-asset
  (mu/merge
    (m/deref :asset/asset)
    [:map [:asset/user [:= "user"]]]))

user-asset
;[:map
; [:asset/id :asset/id]
; [:asset/status :asset/status]
; [:asset/asset {:optional true} [:ref :asset/asset]]
; [:asset/user [:= "user"]]] 

3
ikitommi15:10:35

also, the declarative :merge would allow registries like this:

{:asset/id string?
 :asset/status [:enum :initialized :accepted :active :revoked :failed]
 :asset/asset [:map
               :asset/id
               :asset/status
               [:asset/asset {:optional true} [:ref :asset/asset]]]
 :asset/org-asset [:merge :asset/asset [:map [:asset/org [:= "org"]]]]
 :asset/user-asset [:merge :asset/asset [:map [:asset/user [:= "user"]]]]}

3
ikitommi16:10:45

@shem, you example doesn’t seem legit syntax. But for JSON, you need to defin :decode/json key. Something like:

(m/decode
  [:map
   [:Duration string?]
   [:Start {:decode/json (fn [x] (mt/-string->long (last (re-find #"(\d+)" x))))} string?]]
  {:Duration "kikka"
   :Start "new Date(1413147600000)"}
  (mt/json-transformer))
;{:Duration "kikka"
; :Start 1413147600000}

ikitommi16:10:00

if your new Date is coming from js, it’s a string of Mon Oct 13 2014 00:00:00 GMT+0300 (Eastern European Summer Time) {}, and you need to run it via some date-transforming fn. hope this helps.

ikitommi16:10:17

Would it be a breaking change if m/deref was recursive and would would return the input schema in case it was not a RefSchema? I would like it to be:

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

(m/schema [:schema [:schema [:schema [:map [:x int?]]]]])
; => [:schema [:schema [:schema [:map [:x int?]]]]]

(m/deref (m/schema [:schema [:schema [:schema [:map [:x int?]]]]]))
; => [:map [:x int?]]
, instead of:
(m/deref (m/schema [:schema [:schema [:schema [:map [:x int?]]]]]))
; => [:schema [:schema [:map [:x int?]]]]

ikitommi16:10:36

also:

(m/deref [:map [:x int?]])
; => [:map [:x int?]]
instead of:
(m/deref [:map [:x int?]])
; Execution error (IllegalArgumentException)
; No implementation of method: :-deref of protocol: #'malli.core/RefSchema found for class: malli.core$_map_schema$reify$reify__4587

eraad17:10:09

Hi guys! I’m looking for any pointers on how to transform keys (ie snake to kebab) in nested schemas. I have the following schema:

(def Invoice
  [:map {:registry {::address Address
                    ::tax-line TaxLine}}
   [:issue_date [string? epoch]]
   [:series string?]
   [:sequence [string? {:decode/string mt/-string->long}]]
   [:currency [string? (lookup-ref :currency/code)]]
   [:customer_legal_name string?]
   [:customer_tax_id string?]
   [:customer_tax_id_type [string? (lookup-ref :tax-id/type)]]
   [:customer_email {:optional true} string?]
   [:customer_address {:optional true} ::address]
   [:tax_lines [:vector ::tax-line]]
   [:subtotal [string? {:decode/string mt/-string->double}]]
   [:tax [string? {:decode/string mt/-string->double}]]
   [:total [string? {:decode/string mt/-string->double}]]])
When i do:
(defn coerce-request
  [schema req]
  (if (m/validate schema req)
    (m/decode schema
              req
              nil
              (mt/transformer
               mt/json-transformer
               mt/string-transformer
               {:name :epoch}
               {:name :lookup-ref}
               (mt/key-transformer {:decode k/->kebab-case})))
    (-> schema
        (m/explain req)
        (me/humanize))))

eraad17:10:31

customer_address map keys are not affected

ikitommi18:10:49

@eraad decoding is a process of taking an invalid value and transforming it to a valid (EDN) value. Because of this, the key-transformer runs the transformations on :enter phase. All the top-level map keys are transformed into invalid (kebab)-values and the :customer_address gets a value of :customer-value. After this, the keys don’t match the schema and the child decoders are no-op.

ikitommi18:10:27

if you need to transform values out from the valid (EDN) definitions, you can use m/encode. the key-transformer runs in :leave phase there, in your case as the last thing as it’s also last transformer in the chain.

eraad18:10:28

Hi @ikitommi thank you for the response :thumbsup:

ikitommi18:10:42

here’s a minimal sample:

(m/decode
  [:map {:registry {::address [:map [:street string?]]}}
   [:customer_address ::address]]
  {:customer_address {:street "hämeenkatu"}}
  (mt/transformer
    (mt/key-transformer {:decode #(-> % name str/upper-case keyword)})))
; => {:CUSTOMER_ADDRESS {:street "hämeenkatu"}}

(m/encode
  [:map {:registry {::address [:map [:street string?]]}}
   [:customer_address ::address]]
  {:customer_address {:street "hämeenkatu"}}
  (mt/transformer
    (mt/key-transformer {:encode #(-> % name str/upper-case keyword)})))
; => {:CUSTOMER_ADDRESS {:STREET "hämeenkatu"}}

eraad18:10:41

Nice, thanks!

eraad18:10:12

I’m really loving Malli, tried Spec+Spec-tools for a bit but then tried Malli and its great

👍 3
ikitommi18:10:36

in your case, you can write your own rename-keys like this (running on :leave in decode):

(m/decode
  [:map {:registry {::address [:map [:street string?]]}}
   [:customer_address ::address]]
  {:customer_address {:street "hämeenkatu"}}
  (mt/transformer
    {:decoders {:map {:leave (mt/-transform-map-keys #(-> % name str/upper-case keyword))}}}))
; => {:CUSTOMER_ADDRESS {:STREET "hämeenkatu"}}

ikitommi18:10:04

“on :decode, for :maps run this function f on :leave”. transformers are quite simple in the end 😉

eraad18:10:28

Coo, I was not being aware of :enter and :leave, that’s the missing part