This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-09-22
Channels
- # babashka (106)
- # beginners (29)
- # biff (29)
- # calva (9)
- # cider (6)
- # clj-kondo (24)
- # clojure (40)
- # clojure-europe (94)
- # clojure-japan (1)
- # clojure-nl (1)
- # clojure-norway (45)
- # clojure-uk (13)
- # clojuredesign-podcast (5)
- # clojurescript (12)
- # clr (4)
- # community-development (2)
- # conjure (13)
- # cryogen (4)
- # cursive (4)
- # deps-new (1)
- # fulcro (18)
- # hugsql (2)
- # hyperfiddle (67)
- # jobs (1)
- # malli (47)
- # meander (2)
- # missionary (34)
- # off-topic (1)
- # podcasts-discuss (1)
- # polylith (24)
- # reagent (19)
- # reitit (9)
- # sci (7)
- # shadow-cljs (3)
- # testing (28)
- # tools-deps (1)
- # xtdb (9)
I'm getting some weird errors with my schema with the decoder/transformers and :+
and would love if someone can help me figure this out (commented inside the code block):
(require '[malli.core :as m]
'[malli.transform :as mt])
;; Malli doesn't seem to have a type that can be decoded for bigdec hence this more elaborate schema
(def bigdec-s (malli/-simple-schema
{:type :core/bigdec
:pred (partial instance? BigDecimal)
:type-properties
{:decode/json (fn [x]
(try (bigdec x)
(catch Exception _ x)))}}))
;; :+ doesn't work
(m/decode [:+ [:and bigdec-s pos?]]
["0"]
(mt/transformer
(mt/key-transformer {:decode keyword})
mt/json-transformer))
;; => ["0"]
;; :vector does work
(m/decode [:vector {:min 1} [:and bigdec-s pos?]]
["0"]
(mt/transformer
(mt/key-transformer {:decode keyword})
mt/json-transformer))
;; => [0M]
What's concerning is that m/explain
just throws an exception (also does m/coerce
):
(m/explain [:+ [:and bigdec-s pos?]]
(m/decode [:+ [:and bigdec-s pos?]]
["0"]
(mt/transformer
(mt/key-transformer {:decode keyword})
mt/json-transformer)))
;; => Execution error (ClassCastException) at malli.core/-simple-schema$reify$reify$explain (core.cljc:658).
;; class java.lang.String cannot be cast to class java.lang.Number (java.lang.String and java.lang.Number are in module java.base of loader 'bootstrap')
(m/explain [:vector {:min 1} [:and bigdec-s pos?]]
(m/decode [:vector {:min 1} [:and bigdec-s pos?]]
["0"]
(mt/transformer
(mt/key-transformer {:decode keyword})
mt/json-transformer)))
;; => {:schema [:vector [:and :core/bigdec pos?]],
;; :value [0M],
;; :errors ({:path [0 1], :in [0], :schema pos?, :value 0M})}
I can paste this into the Github repo as an issue if that's preferable@ikitommi do you know of anyone using https://github.com/metosin/malli/blob/master/docs/clojurescript-function-instrumentation.md in a node server instead of browser?
instrumentation is checking value but the errors thrown don’t include/display the explanation
I can workaround by capturing input values in REPL and using m/explain but the jvm DX is so nice I’d love to have it in the node env as well
I just tried it out with a small :node-script
target and just need to change the default report argument from thrower to reporter:
https://github.com/metosin/malli/blob/2a5fc9a6bbf1360881104df2974dc319d3d17431/src/malli/dev/cljs.cljc#L29
in shadow-cljs.edn:
:instrument-node {:target :node-script
:output-to "out/instrument_node.js"
:main malli.instrument-node/main }
the ns:
(ns malli.instrument-node
(:require
[malli.core :as m]
[malli.dev.pretty]
[malli.dev.cljs :as dev]))
(defn plus [x] (inc x))
(m/=> plus [:=> [:cat :int] [:int {:max 6}]])
(defn minus
{:malli/schema [:=> [:cat :int] [:int {:min 6}]]}
[x] (dec x))
(defn main [& args]
(println "in main"))
(dev/start! {:report (malli.dev.pretty/reporter) :skip-instrumented? true})
(comment
(m/function-schemas :cljs)
(plus "hi")
(minus 5)
)
in the terminal where node is running you'll get printed output now instead of exceptions e.g.:
-- Schema Error ------------------------------------------------------------------------------------
Invalid function return value:
4
Function Var:
malli.instrument-node/minus
Function arguments:
[5]
Output Schema:
[:int {:min 6}]
Errors:
{:in [], :message "should be at least 6", :path [], :schema [:int {:min 6}], :value 4}
More information:
----------------------------------------------------------------------------------------------------
not sure about "instrumentation is checking value but the errors thrown don’t include/display the explanation" I'm seeing even with the thrower
I get the same report in the repl output it just doesn't show in the terminal
thanks @U051V5LLP you are right. I need to apologise because I just realised it was my own logging config that was not displaying the explanation in the node env
that said, I have uncovered a challenge in this scenario. I have a try/catch block that is catching the exception thrown by malli.core/-fail
in the catch block I’m trying to log the explain. it’s not in the exception data. I’ve tried using m/explain using the :schema and :inputs data from the exception but it returns nil. can’t figure out why
(mx/defn ^:malli/always combine :- :string
[{:keys [prefix suffix]} :- [:map
[:prefix :int]
[:suffix :string]]]
(str prefix " -> " suffix))
(md/start!)
(try
(combine {:prefix "1" :suffix "a"})
(catch :default e
(let [{:keys [args schema]} (:data (ex-data e))]
(malli.core/explain schema (first args)))))
@ikitommi @U051V5LLP any suggestions on this? I now suspect either mx/defn or ^:malli/always as the problem because they were necessary to reproduce this
no worries @U0510KXTU was going to say if you have a repro.., so thanks : ) yea I think the issue would be in mx/defn
implementation. From a quick scan for any differences vs what dev/start!
is doing it looks like the mx/defn
does not pass the :report
option to -instrument
https://github.com/metosin/malli/blob/30a2176f1893602d8a948ef3103fde91ec5fd638/src/malli/experimental.cljc#L56C39-L57C84
and those reporters are what add the :data
and :type
https://github.com/metosin/malli/blob/30a2176f1893602d8a948ef3103fde91ec5fd638/src/malli/dev/pretty.cljc#L88 to the ex-info
map
so could try adding that to the mx/defn
implementation
also looks like both code paths in mx/defn
(with and without malli/always
) don't add :report
@ikitommi I just tried to fix mx/defn but I get lost in the connections between mx/defn calling -instrument and how that data flows to pretty. could you give me a nudge in the right direction about what needs to change for pretty to have the data it needs from mx/defn? I’ll test the fix locally once it is working and will PR
tbh this shows my lack of macro-foo. I tried requiring pretty into the experimental ns and then creating a pretty/thrower to use in the macro body. but fails at runtime because I haven’t correctly spliced in the thrower fn.
gpt4 tells me that cljs macros can’t use fns because they aren’t constants. at this point I’ll be guessing so I’ll wait for some cljs macro Jedi master instruction
Is there a way to specify a protocol using malli? Something like this:
(defprotocol MyProtocol)
(mc/validate MyProtocol (reify MyProtocol)) ;:malli.core/invalid-schema
I asked a similiar question a few months ago, this is what I have in a common library now:
(defn protocol-schema
"Returns a malli schema that ensures the value satisfies the given protocol."
[prot]
(m/-simple-schema
{:type (:on-interface prot)
:pred #(satisfies? prot %)
:type-properties {:error/fn (fn [error _] (str "Expected value to satisfy " (:on-interface prot) " protocol."))}}))
It MAY be worth considering what happens with protocols being redefined, and change that to take a symbol that is resolved in the pred....
I saw may because I have not confirmed my hunch that it makes my schema a little delicate in the face of protocol redefs
We have a validation middleware set up in our ring app that has historically used json-schema, and i'm introducing malli to it. everything works for the most part, but I'm running into slight awkwardness. I have a POST
endpoint that expects a payload with a string uuid, and in the compojure handler I'm decoding the provided json to clojure/edn. When the endpoint validation was using json-schema, this worked nicely because json-schema expects a string and would verify that it could be parsed to uuid and then the malli decoding transformed the validated string to uuid
. and I could use the same malli schema for both instrumentation and for validation because m.js/transform
converted my :uuid
malli schema to a {"type": "string", "format": "uuid'}
json schema.
Now I'd like to use the same :uuid
schema for both payload validation and instrumentation. is my best bet just to write a (def payload-coercer (m/coercer PayloadSchema (mt/transformer mt/json-transformer)))
and use that in the payload instead of using the schema itself? has anyone else run into this situation?
lol i'm sorry
i think the question is, what's the best way to have a given malli schema represent both input to validate and internal model?
is that possible (or desirable)?
sure, m/coerce
, m/coercer
, m/assert
or just function schemas:
(mx/defn no-op :- PayloadSchema [p :- PayloadSchema] p)
both. i wired up a ring middleware which calls m/validate
on a provided malli schema. i could call m/coerce
, but that's not cached and i'm hoping to avoid writing manual (def ObjCoercer (m/coercer ObjSchema))
for every schema.
maybe i could just write a coercer cache myself
wait what, which routing library are you using? Reitit at least has built-in mw for request and response coercion with cached coercer.
We're using Compojure and we have a homespun url-specs
system that's very reminiscent of Reitit but predates it by a while
i've suggested moving to reitit, but we have roughly 350 endpoints and switching is hard lol
Cached now resolution in compojure is really hard, things like context
bodies are resolved for each request at runtime
just compojure
we're not caching anything in the compojure routes at all, we have a separate map that is filled with entries like this:
[:get "/v0.1/public-invite-codes/:public-invite-code"] {:response #'invites/public-get-response}
and when resolving the request, the validation middleware calls (get url-specs (:compojure/route request))
and then for each of the keys in the spec, it calls the related validation function(when payload-schema (validate-payload route payload-schema payload))
etc
it's pretty simple but does the trick