(ns metabase.xrays.domain-entities.converters (:require [malli.core :as mc] [malli.transform :as mtx] [metabase.util :as u])) | |
(defn- decode-map [schema _] (let [by-prop (into {} (for [[map-key props] (mc/children schema)] [(or (get props :js/prop) (u/->snake_case_en (u/qualified-name map-key))) {:map-key map-key}]))] {:enter (fn [x] (cond (map? x) x (object? x) (into {} (for [prop (js-keys x) :let [js-val (unchecked-get x prop) map-key (or (get-in by-prop [prop :map-key]) (keyword (u/->kebab-case-en prop)))]] [map-key js-val])))) :leave (fn [x] (if (object? x) (throw (ex-info "decode-map leaving with a JS object not a CLJS map" {:value x :schema (mc/form schema)})) x))})) | |
(defn- infer-child-decoder [schema _] (let [mapping (into {} (for [c (mc/children schema)] (if (keyword? c) [(name c) c] [c c])))] {:enter #(mapping % %)})) | |
(defn- infer-child-encoder [schema _] (let [mapping (into {} (for [c (mc/children schema)] (if (keyword? c) [c (name c)] [c c])))] {:enter #(mapping % %)})) | |
(defn- decode-map-of [keydec x] (cond (map? x) x (object? x) (into {} (for [prop (js/Object.keys x)] [(keydec prop) (unchecked-get x prop)])))) | |
(defn- encode-map [x keyenc] (cond (object? x) x (map? x) (reduce-kv (fn [obj k v] (unchecked-set obj (keyenc k) v) obj) #js {} x))) | |
(def ^:private identity-transformers (-> ['string? :string 'number? :number 'int? :int 'double? :double 'float? :float] (zipmap (repeat {:enter identity})))) | |
Malli transformer for converting JavaScript data to and from CLJS data. This is a bit more flexible than a JSON transformer. In particular, it normalizes the keys of On keyword conversion Note that Observe that On Note that On sequences
| (def js-transformer (mtx/transformer {:name :js :decoders (merge identity-transformers {:keyword keyword 'keyword? keyword :qualified-keyword keyword :uuid parse-uuid :vector {:enter #(and % (vec %))} :sequential {:enter #(and % (vec %))} :tuple {:enter #(and % (vec %))} :cat {:enter #(and % (vec %))} :catn {:enter #(and % (vec %))} :enum {:compile infer-child-decoder} := {:compile infer-child-decoder} :map {:compile decode-map} :map-of {:compile (fn [schema _] (let [[key-schema] (mc/children schema) keydec (mc/decoder key-schema js-transformer)] {:enter #(decode-map-of keydec %)}))}}) :encoders (merge identity-transformers {:keyword name 'keyword? name :qualified-keyword #(str (namespace %) "/" (name %)) :uuid str :vector {:leave clj->js} :sequential {:leave clj->js} :tuple {:leave clj->js} :enum {:compile infer-child-encoder} := {:compile infer-child-encoder} :map {:compile (fn [schema _] (let [js-props (into {} (for [[k props] (mc/children schema) :when (:js/prop props)] [k (:js/prop props)])) keyenc (fn [k] (or (get js-props k) (u/->snake_case_en (u/qualified-name k))))] {:leave #(encode-map % keyenc)}))} :map-of {:leave #(encode-map % name)}})})) |
Returns a function for converting a JS value into CLJS data structures, based on a schema. | (defn incoming [schema] ;; TODO This should be a mc/coercer that decodes and then validates, throwing if it doesn't match. ;; However, enabling that now breaks loads of tests that pass input data with lots of holes. The JS ;; tests (as opposed to TS) are particularly bad for this. ;; Don't forget the nested `mc/decoder` calls elsewhere in this file! (mc/decoder schema js-transformer)) |
Returns a function for converting a CLJS value back into a plain JS one, based on its schema. | (defn outgoing [schema] (mc/encoder schema js-transformer)) |