Created
March 22, 2021 14:44
-
-
Save ikitommi/0e5c4e48d8aeb7dd176128856ecdacb5 to your computer and use it in GitHub Desktop.
Malli + Edamame
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(require '[malli.core :as m]) | |
(require '[malli.transform :as mt]) | |
(require '[edamame.core :as e]) | |
(require '[clojure.walk :refer [prewalk]]) | |
(defrecord Wrapped []) | |
(defn parse | |
"Parses an EDN String with edamame and returns a tuple2 of edn-value & path-vec->loc" | |
[s] | |
(let [data (e/parse-string s {:postprocess map->Wrapped}) | |
unwrap-1 (fn [v] (if (instance? Wrapped v) (:obj v) v)) | |
unwrap (fn [v] (prewalk unwrap-1 v)) | |
collect (fn collect [acc path {:keys [loc obj]}] | |
(let [acc (assoc acc path loc)] | |
(cond | |
(map? obj) (reduce (fn [acc kv] | |
(let [k (unwrap (key kv)) | |
acc (collect acc (conj path [:key k]) (key kv))] | |
(collect acc (conj path k) (val kv)))) acc obj) | |
(set? obj) (reduce (fn [acc v] (collect acc (conj path (unwrap v)) v)) acc obj) | |
(vector? obj) (reduce-kv (fn [acc i child] (collect acc (conj path i) child)) acc obj) | |
(sequential? obj) (transduce (map-indexed vector) | |
(completing (fn [acc [i child]] | |
(collect acc (conj path i) child))) acc obj) | |
:else acc)))] | |
[(unwrap data) (collect {} [] data)])) | |
(defn coercer | |
"Takes a schema and optionally transformers and returns a coercer fn of string -> decoded|error, | |
with error containing the edamame loc info under :loc for each actual error" | |
[schema & transformers] | |
(let [decode (m/decoder schema (apply mt/transformer {:name :edamame} transformers)) | |
explain (m/explainer schema)] | |
(fn [s] | |
(let [[data in->loc] (parse s) | |
decoded (decode data)] | |
(or (some-> (explain decoded) | |
(update :errors (partial map #(assoc % :loc (in->loc (:in %))))) | |
(assoc :type ::error) | |
(assoc :string s)) | |
decoded))))) | |
(defn error? [x] (= ::error (:type x))) | |
;; | |
;; | |
;; | |
(def Address | |
[:map | |
[:id string?] | |
[:tags [:set keyword?]] | |
[:address | |
[:map | |
[:street string?] | |
[:city string?] | |
[:zip int?] | |
[:lonlat [:tuple double? double?]]]]]) | |
(def data (slurp "schema.edn")) | |
((coercer Address) data) | |
;{:type :user/error | |
; :schema [:map | |
; [:id string?] | |
; [:tags [:set keyword?]] | |
; [:address | |
; :map | |
; [:street string?] | |
; [:city string?] | |
; [:zip int?] | |
; [:lonlat [:tuple double? double?]]]], | |
; :value {:tags #{":hotel" :coffee :artesan}, | |
; :address {:lonlat [61.4858322 23.7854658] | |
; :city "Tampere" | |
; :street "Ahlmanintie 29" | |
; :zip "33100"}, | |
; :id "Lillan"}, | |
; :errors (#Error{:path [:tags 0], | |
; :in [:tags ":hotel"], | |
; :schema keyword?, | |
; :value ":hotel", | |
; :loc {:row 2, :col 27, :end-row 2, :end-col 35}} | |
; #Error{:path [:address :zip], | |
; :in [:address :zip], | |
; :schema int?, | |
; :value "33100", | |
; :loc {:row 5, :col 17, :end-row 5, :end-col 24}}), | |
; :string "{:id \"Lillan\" | |
; :tags #{:artesan :coffee \":hotel\"} | |
; :address {:street \"Ahlmanintie 29\" | |
; :city \"Tampere\" | |
; :zip \"33100\" | |
; :lonlat [61.4858322, 23.7854658]}} | |
; "} | |
((coercer Address (mt/string-transformer)) data) | |
{:id "Lillan" | |
:tags #{:coffee :artesan ::hotel}, | |
:address {:street "Ahlmanintie 29" | |
:zip 33100 | |
:city "Tampere" | |
:lonlat [61.4858322 23.7854658]}} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment