This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-01-31
Channels
- # announcements (22)
- # asami (19)
- # aws-lambda (4)
- # babashka (42)
- # beginners (43)
- # calva (28)
- # cider (1)
- # clerk (79)
- # clj-kondo (12)
- # clojure (47)
- # clojure-berlin (1)
- # clojure-brasil (1)
- # clojure-dev (12)
- # clojure-europe (40)
- # clojure-nl (2)
- # clojure-norway (5)
- # clojure-uk (3)
- # clojurescript (56)
- # clr (12)
- # conjure (8)
- # cursive (4)
- # datomic (78)
- # dev-tooling (6)
- # exercism (1)
- # fulcro (9)
- # hoplon (3)
- # jobs (3)
- # jobs-discuss (4)
- # lambdaisland (3)
- # leiningen (1)
- # london-clojurians (1)
- # lsp (125)
- # malli (32)
- # matcher-combinators (3)
- # nrepl (1)
- # off-topic (6)
- # pathom (39)
- # re-frame (13)
- # releases (2)
- # remote-jobs (3)
- # sci (7)
- # shadow-cljs (117)
- # sql (6)
- # squint (7)
- # tools-build (15)
- # tools-deps (12)
What’s the correct way to handle this?
(malli/explain [:map [:tag [:enum :foo]]] (clojure.data.xml/sexp-as-element [:foo]))
;;=> Execution error (AbstractMethodError) at clojure.data.xml.node.Element/entryAt (node.cljc:-1).
;; Method clojure/data/xml/node/Element.entryAt(Ljava/lang/Object;)Lclojure/lang/IMapEntry; is abstract
Do I need to walk the return value of sexp-as-element
, transforming clojure.data.xml.node.Element
s into maps before handing them to Malli, or is there a smarter way?Something like this, perhaps?
(malli/explain Schema (malli/decode Schema xml (malli.transform/transformer {:name :my-custom-thing})))
:thinking_face: looks like the Element
is not implementing clojure.lang.Associative
, which malli programs against. If you could PR that into the data.xml
, I should just work.
otherwise, you need to transform it, works, but much slower, e.g.
(m/coerce
[:map {:type 'clojure.data.xml.node.Element} [:tag [:enum :foo]]]
{:tag :foo}
custom-transformer-to-transform-the-thing)
or:
(m/coerce
[:map {:decode/xml ...} [:tag [:enum :foo]]]
{:tag :foo}
(mt/transformer {:name :xml}))
Thanks! Yeah, that’s pretty much what I ended up with. Performance isn’t critical in this case, so this’ll do just fine. 👍
This solution doesn’t work with nested [:map ,,,]
s, unfortunately:
(def Schema
[:map {:decode/xml (partial into {})}
[:tag [:enum :foo]]
[:content
[:+ [:map {:decode/xml (partial into {})}
[:tag [:enum :bar]]
[:content [:+ string?]]]]]])
;;=> #'user/Schema
(malli/coerce Schema
{:tag :foo :attrs {} :content [{:tag :bar :attrs {} :content ["1"]}]}
(transform/transformer {:name :xml}))
;;=> {:tag :foo, :attrs {}, :content [{:tag :bar, :attrs {}, :content ["1"]}]}
(try
(malli/coerce Schema
{:tag :foo :attrs {} :content [{:tag :BAD :attrs {} :content ["1"]}]}
(transform/transformer {:name :xml}))
(catch clojure.lang.ExceptionInfo ex
(-> ex ex-data :data :explain error/humanize)))
;;=> {:content [{:tag ["should be :bar"]}]}
(try
(malli/coerce Schema
(xml/sexp-as-element [:foo [:bar "1"]])
(transform/transformer {:name :xml}))
(catch clojure.lang.ExceptionInfo ex
(-> ex ex-data :data :explain error/humanize)))
;;=> {:tag :foo, :attrs {}, :content [{:tag :bar, :attrs {}, :content ["1"]}]}
(try
(malli/coerce Schema
(xml/sexp-as-element [:foo [:BAD "1"]])
(transform/transformer {:name :xml}))
(catch clojure.lang.ExceptionInfo ex
(-> ex ex-data :data :explain error/humanize)))
;;=> Execution error (AbstractMethodError) at clojure.data.xml.node.Element/entryAt (node.cljc:-1).
;; Method clojure/data/xml/node/Element.entryAt(Ljava/lang/Object;)Lclojure/lang/IMapEntry; is abstract
Dunno whether custom-transformer-to-transform-the-thing
would solve this problem, but I’m unclear on how I’d implement that, since I can’t find any documentation or examples on how to implement custom transformers like that.Something like:
(def elements-to-maps-transformer
"transforms all Elements into maps"
(mt/transformer
{:decoders {:map #(cond->> % (instance? Element %) (into {}))}}))
1. apply only to :map
schemas
2. run a function that checks if it’s an Element
, then make it a map
… and you can compose, the order matters:
(mt/transformer
elements-to-maps-transformer ;; this first
(mt/json-transformer)) ;; ... so these see maps, not emelents
Thanks, I was missing the :map
bit. 👍 Still no dice, though, unfortunately: Malli only applies the transformation to the root value.
I think the simplest solution in this case is for me to walk the thing sexp-as-element
returns and turn Element
s to maps before handing the value to Malli.
oh, true. into (into {} x)
is not recursive. change it to (clojure.walk/postwalk #(cond->> % (instance? Element %) (into {})))
and should work
Yep, could do that, but I don’t think that has any benefit over just transforming the value beforehand in this case.
FWIW, https://ask.clojure.org/index.php/12625/clojure-element-doesnt-implement-clojure-associative-entryat.
Not sure what the final solution was on this thread. In my case I’m invoking m/schema
and getting clojure.lang.ExceptionInfo: :malli.core/invalid-schema
if we have maps like these, where the static* keys are known and the numbers ending the var* keys are not known and may be any integer, is it possible to construe a schema that also covers the var* keys?
you could use :map-of
and a predicate schema to constrain the names of the var-*
keys, then :merge
with the static keys
(def samppeli {:static1 "foo"
:static2 "bar"
:var-1234a "baz"
:var-9725 "euromokko"})
(def varskeema (m/schema [:map-of #"^var-\d+$" :string]))
(def staticskeema (m/schema [:map [:static1 :string]
[:static2 :string]]))
(def skeema (mu/merge varskeema staticskeema))
this validates ok even though it shouldn't, probably because maps are open by default. i can't seem to find the right place for {:closed true}
Try using mu
/`update-properties` on the results of mu/merge
with the experimental syntax is there a way to specify a unique return type per arity? I only see one in the tests/examples.
(mx/defn a-fun ;; :- [:int {:min 0}] <--- instead of this
"docstring"
([x :- [:int {:min 0}]] :- :int (inc x))
([x :- [:int {:min 0}], y :- :int] :- [:int {:min 10}] (+ x y))
([x :- [:int {:min 0}], y :- :int & zs :- [:* :int]] :- :int (apply + x y zs)))
something like that, I'm not sure where the syntax would go. With :function
schema annotations this is supported, so it's just a matter of where the syntax lives and how it's transformed.I don’t think so. I was looking at that the other day. notice that in the tests there is no such thing.
Been noodling on this one for a while. I think I have a solution, but would be nice to hear thoughts: I want to conditionally validate fxn annotations for input/output based on the value of an atom (a switch to turn it off or on). I am thinking of 3 modes: 1. usually : validate iff @*validate-schemas. then either of these will override *validate-schemas: 2. ^:never-validate : never validates 3. ^:always-validate : always validates spoiler: Currently I am emitting a call to instrument!, but I am thinking that it makes sense to use -strument, and use a function in mode… Then again it’s probably better to just require the user to specify their auto validation wants before we start defining functions.