This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2020-10-25
Channels
- # announcements (1)
- # architecture (3)
- # beginners (31)
- # calva (61)
- # cider (1)
- # clojure (43)
- # clojure-dev (17)
- # clojure-europe (85)
- # clojure-uk (8)
- # clojurescript (31)
- # cryogen (2)
- # cursive (7)
- # data-science (12)
- # datalog (1)
- # datomic (4)
- # defnpodcast (1)
- # figwheel-main (11)
- # fulcro (32)
- # hoplon (1)
- # leiningen (1)
- # malli (47)
- # pedestal (1)
- # rdf (2)
- # re-frame (11)
- # reagent (4)
- # reitit (7)
- # shadow-cljs (22)
- # vrac (8)
- # xtdb (2)
https://www.reddit.com/r/Clojure/comments/jhq899/did_anyone_migrate_a_nontrivial_project_from_spec/
I did, yes it is more involved 🙂
To be more specific, this project is a (conceptual) port from a clojure.spec project https://github.com/jeroenvandijk/aws.cloudformation.malli
I was generating the clojure.spec definitions from the AWS Cloudformation spec. This generation part was much easier with Malli
There are enough differences that I cannot consider it as a 1 on 1 port
Feel free to respond on Reddit as this was not my own question, just forwarded it here
yeah thanks. I’m not using reddit. I hope the reddit user finds it here 😅
@ikitommi I think I asked this before, but I can't find any docs on this, nor can I find the conversation on Zulip. So here it goes:
(def Schema
[:map [:x int?]])
(defrecord Wrapper [obj loc])
parsed
;;=> {#script.Wrapper{:obj :x, :loc {:row 1, :col 2, :end-row 1, :end-col 4}} #script.Wrapper{:obj 1, :loc {:row 1, :col 5, :end-row 1, :end-col 6}}}
(defn wrapper-transformer []
(mt/transformer
{:name :wrapper
:default-decoder :obj
:default-encoder (fn [obj]
(->Wrapper obj nil))}))
(prn (m/decode Schema parsed wrapper-transformer)) ;;=> nil ... ???
I tried this:
(defn unwrap [x]
(if (instance? Wrapper x)
(:obj x)
x))
(defn wrapper-transformer []
(mt/transformer
{:name :wrapper
:default-decoder unwrap
:default-encoder (fn [obj]
(->Wrapper obj nil))}))
but this just returns the entire object itselfthe problem is that the decoder is first given a map, it calls :obj
on it, which return nil.
so, with malli, you would need to decode all the keys & values on :map
step also. doable, but extra noise 😞
(ns user
(:require [malli.core :as m]
[malli.transform :as mt]))
(def Schema
[:map [:x int?]])
(defrecord Wrapper [obj loc])
(def parsed {(map->Wrapper {:obj :x, :loc {:row 1, :col 2, :end-row 1, :end-col 4}})
(map->Wrapper {:obj 1, :loc {:row 1, :col 5, :end-row 1, :end-col 6}})})
(defn unwrap [x]
(if (instance? Wrapper x) (:obj x) x))
(defn wrapper-transformer []
(mt/transformer
{:name :wrapper
:decoders {:map (fn [x] (reduce-kv (fn [acc k v] (assoc acc (unwrap k) (unwrap v))) {} x))}
:default-decoder unwrap
:default-encoder (fn [obj] (->Wrapper obj nil))}))
(m/decode Schema parsed wrapper-transformer) ;;=> nil ... ???
; => {:x 1}
Hmm, in spec I would maybe have to spec a key as ::wrapped-int
and then coerce it after it was checked?
can the wrapped by anywhere? Wrapped map/vector/set? All values are wrapped (any nested edn value)? Or just keys and values in the map?
@ikitommi The use case is preserving location information for non-iobjs and using that for error messages while validating malli schemas
e.g. you want to validate an EDN file and you get an error: this should be an int, on line 5, row 12
@ikitommi This is the complete code: deps.edn:
{:deps {metosin/malli {:mvn/version "0.2.1"}
borkdude/edamame {:git/url ""
:sha "ba93fcfca1a0fff1f68d5137b98606b82797a17a"}}}
(ns script
(:require [edamame.core :as e]
[malli.core :as m]
[malli.transform :as mt]))
(def Schema
[:map [:x int?]])
(defrecord Wrapper [obj loc])
(defn iobj? [x]
(instance? clojure.lang.IObj x))
(def parsed
(e/parse-string "{:x 1}"
{:postprocess
(fn [{:keys [:obj :loc]}]
(if (iobj? obj)
(vary-meta obj merge loc)
(->Wrapper obj loc)))}))
(defn unwrap [x]
(if (instance? Wrapper x)
(:obj x)
x))
(defn wrapper-transformer []
(mt/transformer
{:name :wrapper
:default-decoder unwrap
:default-encoder (fn [obj]
(->Wrapper obj nil))}))
;; (prn parsed)
(prn (m/decode Schema parsed wrapper-transformer))
@ikitommi Found the conversation here: https://clojurians.zulipchat.com/#narrow/stream/180378-slack-archive/topic/malli/near/206235546
@ikitommi So the way it can work is:
(defn fail! [schema {:keys [:obj :loc]}]
(throw (ex-info (str obj " did not satisfy " schema
" [at " (str (:col loc)":"(:row loc)) "]") {})))
(def <42 [:and 'int? [:< 42]])
(defn lift-non-iobj-schema [schema]
[:map {:encode/success :obj,
:encode/failure (partial fail! schema)} [:obj schema]])
(def Schema [:map [:a (lift-non-iobj-schema <42)]])
(def valid? (m/validator Schema))
(def success (m/encoder Schema (mt/transformer {:name :success})))
(def failure (m/encoder Schema (mt/transformer {:name :failure})))
Syntax error (ExceptionInfo) compiling at (script.clj:32:1).
42 did not satisfy [:and int? [:< 42]] [at 5:1]
The downsize of this is that I have to wrap schemas myself in case I want to check something non-iobj-ish (strings, keywords, numbers)
And this doesn't seem to work for keywords for example:
(def <42 [:and 'int? [:< 42]])
(defn lift-non-iobj-schema [schema]
[:map {:encode/success :obj,
:encode/failure (partial fail! schema)}
[:obj schema]])
(def Schema [:map [(lift-non-iobj-schema :a) (lift-non-iobj-schema <42)]])