Fork me on GitHub
#malli
<
2022-12-15
>
rmxm00:12:32

another quick question, similar to the last one, why?

(m/properties [:enum "x" "y" {:error/message "a"}]) ;; nil
(m/properties [:string {:error/message "a"}]) ;; #:error{:message "a"}

ikitommi06:12:40

properties map should be the second element in the vector syntax, not the last.

rmxm18:12:35

thanks a lot, yeah silly mistake... but more and more I get more confident I will be able to express any crazy schema + have validating/schema/errors in one shape, thanks for your help

lepistane01:12:26

I have a question. So when i am using spec to describe paramenters with reitit.ring.middleware.multipart/temp-file-part there is swagger/type which can be shown in the swagger page. I can't seem to find a way to do this with malli (yes i swapped coercion) I tried https://github.com/metosin/reitit/blob/master/modules/reitit-malli/src/reitit/ring/malli.cljc this breaks page so that you can't even put params anymore

:parameters {:query [:map {:json-schema {:type "file"}}
                                     [:filename string?]
                                     [:content-type string?]
                                     [:size int?]
                                     [:tempfile [:fn (partial instance? )]]]
this is the gist Is this solvable or i am stuck with spec? after some tryouts this worked
[:filename {:swagger/type "file"} string?] 
but i am wondering am i making a mistake and this should be done some other way? edit https://github.com/metosin/reitit/blob/master/examples/ring-malli-swagger/src/example/server.clj provided all answers i needed, didn't see it first time i checked examples 🙂 thankful and grateful for the examples gratitude

👍 1
lepistane03:12:06

I guess i didn't find answer to all questions ... So i have custom schema that uses custom function for special case validation.

[:map {:closed true} 
 [:match-id [:fn #:error{:message "Invalid match-id"} #function[valid-uuid-as-str?]]] 
 [:date [:fn #:error{:message "Invalid date"} #function[valid-date-as-str?]]]]
But it doesn't work. I am already using this specialized validation in other places but i was thinking about reusing it all everywhere. is it possible that reitit parameter validation can be only primitive predicates (from clj.core and malli) ?

ikitommi06:12:07

there are no restrictions on reitit for using malli, it should just work. let me test that.

ikitommi06:12:39

tested and it just works :thinking_face:

ikitommi06:12:16

if you can do a minimalist repro, I can check what’s wrong with it.

lepistane11:12:09

@ikitommi Thanks! Here is the repo (just reused existing mali reitit example and added on top) https://github.com/StankovicMarko/reitit/blob/custom-validation/examples/ring-malli-swagger/src/example/server.clj You can see what i added on top: https://github.com/StankovicMarko/reitit/commit/8f6634d1f7529aebd23e6527ac738857894b0328 Main issue is that when i put values for GET plus, if they are wrong i can't make a request until i put correct value. picture A And for test route i can add whatever i want and i dont want that. I want to prevent it just like with 'native' types picture B

lepistane11:12:32

What am i doing wrong?

lepistane12:12:41

This was the only i managed to do it

[:map
                            [:date {:description "Date when match happened"
                                    :json-schema/type "string"
                                    :json-schema/format "date"
                                    :json-schema/default "2022-10-10"}
                             [:and
                              [:re #"^\d{4}\-(0[1-9]|1[012])\-(0[1-9]|[12][0-9]|3[01])$"]
                              [:fn {:error/message "Invalid date"}
                               v/valid-date-as-str?]]]]
Use regex to enforce format of a string but feels wrong. shouldn't this already be available? https://swagger.io/docs/specification/data-models/data-types/#string

lepistane12:12:10

Am i missing something?

Jordan Robinson15:12:08

Hello, I'm in the process of porting over a codebase from spec to malli, and it's very cool! Thanks for all your work. I have a small question that may be a bit stupid so forgive my ignorance, is there a way to pick up the seed or size values from within a :gen/fmap function?

escherize16:12:34

What I have done, is use a deterministic transformation on the generated value in :gen/fmap

Jordan Robinson16:12:33

that sounds like it might be along the lines of what I'm looking for, I don't suppose you have a code snippet at all?

escherize16:12:27

its not a stupid question, let me see here

escherize16:12:04

(mg/generate
 [:and {:gen/fmap #(str "my_" % "_thing")} string?]
 {:seed 10, :size 10})

escherize16:12:07

something like that work for you?

Jordan Robinson16:12:23

ah I see, I did try something like that at first but it didn't really work for my situation, let me try and write some pseudocode for what I have

(malli/schema [:fn
                 {:gen/fmap
                  (fn [_]
                    (generate-thing {:seed ???}))}
                 (fn [thing] (validate-thing thing))])
which I'm then calling via (mg/generate schemas/thing {:seed 0})

Jordan Robinson16:12:41

so I'm not sure that would work here, does that make a bit more sense?

escherize16:12:29

can you build up a generator, and supply it in the :gen/gen property?

escherize16:12:45

meaning a clojure.test.check.generators

Jordan Robinson16:12:10

potentially yes, but this isn't actually in test code, so I'd really only rather use clojure.test.check.generators as a last resort since that would mean putting it in the src imports which, feels a bit funky

Jordan Robinson16:12:30

if it's not a common thing what I'm doing though I guess I'd have to go down that route

escherize16:12:57

I guess it depends how hard it is to generate your thing

Jordan Robinson16:12:13

not hard but it does take an int seed

Jordan Robinson16:12:18

and I'm not sure how to pass that through

Jordan Robinson16:12:36

since otherwise if I call it without the seed in tests, well, it's random 😅

escherize16:12:51

is it data? it might be worth using malli to describe it

escherize17:12:01

then you will get easier generation

Jordan Robinson17:12:00

that's a good point, I guess I'm looking for an easy way out as we already have (generate-thing)

escherize17:12:19

how about…

escherize17:12:52

(mg/generate
 [:and {:gen/fmap #(generate-thing %)} int?]
 {:seed 10})

escherize17:12:22

probably not super good, since the schema is meaningless now

Jordan Robinson17:12:25

aha I wasn't sure if that would work, as if I use that to validate as well will it try and think the input should also be an int?

escherize17:12:36

you can use it to generate..

escherize17:12:57

(mg/generate
 [:and {:gen/fmap #(generate-thing %)} 
  [:or int? [:fn ..check for mything..]]
 {:seed 10})

Jordan Robinson17:12:10

ahh that's interesting

escherize17:12:10

really weird though

Jordan Robinson17:12:18

yes very weird, 😅 I guess that's how my brain went to trying to get a seed value in at first

escherize17:12:32

yeah not sure it’s super easy

escherize17:12:47

actually.. in:

(malli/schema [:fn
                 {:gen/fmap
                  (fn [_]
                    (generate-thing {:seed ???}))}
                 (fn [thing] (validate-thing thing))])

escherize17:12:58

what would be getting mapped over?

Jordan Robinson17:12:24

ah nothing, but it does work, I ripped it from the malli test code

Jordan Robinson17:12:58

this is the relevant example:

(is (= 42 (mg/generate [:re
                            {:gen/fmap (fn [_] 42)
                             :gen/schema :int}
                            #"abc"]))

escherize17:12:16

but you can generate values from a regex

escherize17:12:25

i guess you can generate values form a :fn schema too

Jordan Robinson17:12:09

thanks very much for your help btw, I'm about to finish today but you've given me some very interesting ideas

escherize17:12:52

Nice! yeah I am starting on porting a giant codebase from spec and schema (with plenty of custom macros) to malli. Fun times!

Jordan Robinson17:12:14

I'm in somewhat of a similar situation, it's fun till it isn't has been my experience so far 😅

escherize17:12:27

hope your pulse doesn’t rise too much over it 😛

👍 1
Jordan Robinson11:12:53

as an update on this, I ended up writing a custom generator using the clojure test check generators in the end, thanks again for all your help

lepistane16:12:05

I am unable to find a solution to what should be simple thing. How do i convert date string to LocalDateTime object automatically? (i am assuming this is request coercion) This is my muuntaja instance

(def muuntaja-instance
  (m/create
   (-> m/default-options
      (assoc-in [:formats "application/json" :decoder-opts] {:decode-key-fn csk/->kebab-case-keyword
                                                             <<???>> #(java.time.LocalDate/parse %)})
      (assoc-in [:formats "application/json" :encoder-opts] {:encode-key-fn csk/->camelCaseString
                                                             :date-format "yyyy-MM-dd"}) 

Ben Sless17:12:46

You have to define your own schema type for that, unfortunately, but it's pretty easy

escherize17:12:08

yesterday I wrote an x/defn macro that behaves like Schema’s s/defn. (mx/defn does not throw with incorrect :in or :out.) Is there a library or project to mirror little utilities like that in Schema or Spec?

escherize17:12:19

What other migration strats have you found useful?

dvingo17:12:16

mx/defn should throw when instrumentation is enabled

☝️ 1
escherize18:12:19

snoop is cool, but i want to stick to vanilla ways of doing things

escherize18:12:49

my macro just calls instrument at defn creation time

escherize18:12:02

with a specific filter to match only the current function

ikitommi18:12:44

wanted to keep mx/defn slim. thought of adding the Schema-style :always|never-validate hints too.

escherize19:12:19

how would you implement the never validate mode?

escherize19:12:22

I have a macro emitting the function with an extra {::validate! id} metadata and emitting an instrumentation filter after doing the core/defn call. so that’s a way to do the always-validate behavior

escherize19:12:38

not sure how to guard it, maybe add something into mi/-strument where it filters out on a certain fxn metadata value

Ben Sless18:12:08

@ikitommi taking the coercer discussion here to thread

Ben Sless18:12:54

I assume the 1 arity for coercer decodes or throws, would be nice if we could have 3 arity for on success and on failure continuations, where the on-success takes the decoded value and on-failure takes the explained result

🧠 1
ikitommi18:12:48

sure, that would work as the arities woudn’t clash.

ikitommi18:12:45

want to make a PR?

ikitommi18:12:53

I like the idea. control to the user.

Ben Sless18:12:01

Sketch for more robust time schemas

Ben Sless18:12:27

@ikitommi feedback / ideas before I pursue this path further?

Ben Sless18:12:51

And thoughts regarding adding an epoch transformer?

ikitommi06:12:35

Looks good. Quick comments: • cljs + bundle size. If you want to use malli.time, it will brings the generators in, as multimethods don't get DCEd, it's a lot of extra. README defines how to check the bundle size reports. Option to have malli.time and malli.time.generators , which sucks too. • If this is only for Clj now, then not a big issue. Could be then malli.experimental.time - when temporal/cljs solution comes, it most likely changes a bit. • Schema types could be namespaced with time, e.g. :time/local-date, thinking of doing same for malli.util schemas.

Ben Sless07:12:55

• cljs: not really sure how to approach that so I just punted and did clj only • will move to experimental for now • namespacing: I thought I did

ikitommi09:12:15

namespace, so you did, didn’t notice, brilliant! 🙂

juhoteperi09:12:31

I think it would be OK to merge and maybe even release clj-only version first

juhoteperi09:12:54

Though there is some danger in that case that something in the API will be hard to implement on Cljs side

juhoteperi09:12:49

I could explore using date-io as a facade to different JS date libs, so users can use whatever they want: https://github.com/dmtrKovalenko/date-io#projects

juhoteperi09:12:28

Adding clj-only as experimental ns sounds good to me

👍 1
valtteri15:12:34

Personally I’m a bit concerned about the longevity of date-io and would appreciate malli.time to be built upon something that will last over time. However I trust @U061V0GG2 ‘s opinion over mine 🙂

juhoteperi15:12:41

Yeah the issue about Temporal support on date-io shows the the API isn't very well designed

juhoteperi15:12:29

But building our own thing to to support different libs isn't much better either

Ben Sless05:12:04

Besides separating out generators code and providing acceptors for json schema, any other things to keep in mind?

Ben Sless11:12:02

Draft MR is ready, your feedback is appreciated https://github.com/metosin/malli/pull/802

Ben Sless05:12:42

@ikitommi I think it's ready for review, besides adding tests the design is pretty settled. Only thing I'm not sure about is the parsing code which exists in the core ns.

escherize20:12:22

Before I build it myself — Is there a way to get a descirption of a schema in malli.core? e.g. [;map [:x int?]] => “A map containing: x, an integer”

escherize22:12:59

If it did, Given a malli schema, it should return a string with a description of the shape it expects.

escherize22:12:43

Are there problems with doing that? I figure a call to m/ast, then dispatching on :type should be sufficient. wdyt?

ikitommi05:12:24

you should use m/walk + dispatch on type. Each Schema implements it's own -walk so all the children are walked correctly, see https://github.com/metosin/malli/blob/master/src/malli/json_schema.cljc on how to do this.

ikitommi05:12:52

but, an interesting idea!

escherize16:12:10

Could you help me understand the benefits of using walk vs ast? Is walk more stable?

ikitommi16:12:50

All schemas implement the m/-walk. So you can walk over any schema, even those defined in user space. (m/walk schema (m/schema-walker identity)) works always. With AST, you have to know all schemas ahead of time to know how to handle those. In your use case, I guess you need to handle all schemas anyway, you can do the same with AST. Just that it breaks if someone changes the AST for a schema (each schema controls how it looks). There is a disclaimer in Malli README not to use the AST as a persistence model, could change, no no plans to break it and will avoid doing that. We are using AST walking too in projects. But, walk is always safe.

escherize16:12:58

Thanks for explaining that 🙏 I am looking into walk and schema-walker now.

👍 1
escherize17:12:23

Here’s a self contained example. I used cond since its easier to fit. I’ll actually use a multimethod though

(mc/walk
 [:map [:x :int] [:y :string]]
 (fn [schema _path children _options]
   (println "-------")
   (println "schema:" schema)
   (println "children:" children)
   (cond
     (= (mc/type schema) :int) "INTEGER"
     (= (mc/type schema) :string) "STRING"
     (= (mc/type schema) :map) (str "map of "
                                    (str/join "" (mapv (fn [[k _ v]] (pr-str [k v]))
                                                       children)))
     :else schema)))

escherize17:12:58

I don’t think schema-walker is the way to go, since I dont want to ultimately return a schema

escherize17:12:41

maybe I can attach the stringified explaination to the schema as a property, and look for the prop on children?

ikitommi17:12:45

I would copy the malli.json-schema and start from that

ikitommi17:12:50

it's a postwalk, you get the transformed children to your callback, should be straightforward to implement what you want

ikitommi06:12:35

Looks good. Quick comments: • cljs + bundle size. If you want to use malli.time, it will brings the generators in, as multimethods don't get DCEd, it's a lot of extra. README defines how to check the bundle size reports. Option to have malli.time and malli.time.generators , which sucks too. • If this is only for Clj now, then not a big issue. Could be then malli.experimental.time - when temporal/cljs solution comes, it most likely changes a bit. • Schema types could be namespaced with time, e.g. :time/local-date, thinking of doing same for malli.util schemas.