This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-02-22
Channels
- # announcements (88)
- # autochrome-github (2)
- # babashka (26)
- # beginners (5)
- # biff (2)
- # cider (73)
- # clj-kondo (4)
- # cljsrn (6)
- # clojure (54)
- # clojure-art (3)
- # clojure-europe (73)
- # clojure-germany (5)
- # clojure-new-zealand (1)
- # clojure-nl (13)
- # clojure-norway (16)
- # clojure-uk (8)
- # clojurescript (73)
- # conjure (1)
- # core-async (10)
- # cursive (17)
- # datahike (51)
- # datalevin (21)
- # datomic (4)
- # emacs (2)
- # events (3)
- # fulcro (35)
- # honeysql (6)
- # introduce-yourself (1)
- # jackdaw (3)
- # jobs (1)
- # leiningen (4)
- # lsp (3)
- # malli (17)
- # off-topic (60)
- # other-languages (5)
- # pathom (17)
- # pedestal (3)
- # polylith (19)
- # portal (2)
- # practicalli (1)
- # rdf (14)
- # reitit (3)
- # releases (1)
- # reveal (9)
- # sci (1)
- # shadow-cljs (26)
- # spacemacs (17)
- # sql (4)
- # testing (10)
- # tools-build (6)
- # tools-deps (16)
- # vim (9)
I'm getting data to a Reitit handler with malli coercion from a simple HTML form. One of the fields should either be a number or be left blank when the form is submitted. I want to translate that to either nil
or to remove the key entirely from the map during coercion, is that possible?
Right now coersion fails because the feild holds a string when it should hold an int, but just defining the value of the field to be [:or string? int?]
means that all other functions have to deal with the possibility of that field being a non-int.
you could make your own schema type and then add custom coercion logic there. For example this is a custom schema for local dates:
:local-date (m/-simple-schema
{:type :local-date
:pred tu/date?
:type-properties {:gen/gen gen-date
:decode/string (fn [v]
(log/info "Decoding to local-date: " v)
(tu/date v))}})
and add that to your malli registryand to deal with the logic of conditionally removing the key from a map, my guess is that you could change the malli coercer: https://github.com/metosin/reitit/blob/master/doc/coercion/malli_coercion.md#configuring-coercion by changing the transformers
Interesting, I like that first idea. Make a type with a name like :nilable-form-int, simple enough. Thank you! Writing a coercer would probably be better, but I don't think I could wrap my head around it fast enough to still please the deadline.
@rovanion maybe:
(def NilableInt
[:maybe {:decode/string (fn [x] (when-not (str/blank? x) (mt/-string->long x)))} :int])
(m/decode
[:map
[:x NilableInt]
[:y NilableInt]]
{:x ""
:y "123"}
(mt/string-transformer))
; => {:x nil, :y 123}
(mg/sample
[:map
[:x NilableInt]
[:y NilableInt]])
;({:x -1, :y nil}
; {:x nil, :y nil}
; {:x 0, :y -1}
; {:x nil, :y nil}
; {:x 2, :y nil}
; {:x -3, :y -1}
; {:x -2, :y nil}
; {:x 3, :y 1}
; {:x nil, :y nil}
; {:x -8, :y -1})
Hah, was just writing that exact :decode/string-function in my own simple-schema. But yeah, that fits the bill!
I really like that Clojure has functions like when-not
in the standard library so I don't have to make them up in some util library.
This is where I'm at now:
(register! :html/nilable-int
(malli/-simple-schema
(fn [opts _]
{:type :html/nilable-int
:pred (some-fn int? nil?)
:property-pred (malli/-min-max-pred nil)
:description "An int or nil. Empty string is transformed to nil."
:type-properties {:decode/string (fn [x] (when-not (string/blank? x)
(mtransform/-string->long x)))
:gen/gen (gen/large-integer* opts)}})))
I just have to write a generator that generates nil sometimes. Sadly :gen/schema [:maybe [int? opts]]
doesn't respect :min passed in opts.The practical implications of the distinction is lost on me, I thought both were resolved to the exact same schema.
Either way it made no difference, setting :gen/schema [:maybe [:int opts]]
still generated negative numbers when opts
was {:min 0}
.
(mg/sample [:any {:gen/schema [:int {:min 100}]}])
; => (101 101 101 101 102 108 105 100 111 148)
(mg/sample [:maybe [:any {:gen/schema [:int {:min 100}]}]])
; => (nil nil nil 102 nil nil 102 103 111 159)
Sorry, I must have failed to press C-c C-c or something after switching from int?
to :int
because it's working now and I don't know what else could have caused it to fail.
Thank you so much for your time.
This is the schema I ended up with, does both generate, transform and conform as I want it:
(register! :html/nilable-int
(malli/-simple-schema
(fn [opts _]
{:type :html/nilable-int
:pred (some-fn int? nil?)
:property-pred (malli/-min-max-pred nil)
:description "An int or nil. Empty string is transformed to nil."
:type-properties {:decode/string (fn [x] (when-not (string/blank? x)
(mtransform/-string->long x)))
:gen/schema [:maybe [:int opts]]}})))
So been playing a bit more and built up some snippet code to show my issues, the first works as I expect the second part does not seem to transform in the same way and I can not figure out why, can any one lend some assistance ?
(def my-spec
[:map {:closed true}
[:object/type [:keyword {:json-schema/type "keyword" :string-schema/type "keyword"}]]])
(m/decode
my-spec
{:object/type "test"}
mt/json-transformer)
;; => #:object{:type :test}
How ever using this returns an error complaining that objecct/type is not a keyword, I was under the imrpression it should be converted the same as m/decode, so I am obviously missing a trick some where, tried a few things but just can not get it to decode.
(reitit.coercion.malli/create
{:transformers {:body {:default reitit.coercion.malli/default-transformer-provider
:formats {"application/json" reitit.coercion.malli/json-transformer-provider
"application/transit+json; charset=utf-8" reitit.coercion.malli/json-transformer-provider
"application/transit+json" reitit.coercion.malli/json-transformer-provider}}
:keyword {:default reitit.coercion.malli/string-transformer-provider}
:string {:default reitit.coercion.malli/string-transformer-provider}
:response {:default reitit.coercion.malli/default-transformer-provider}}})