Fork me on GitHub
#malli
<
2020-02-14
>
ikitommi08:02:23

I'm bit surprised that the first one doesn't work.

plexus14:02:38

I'm trying to use malli to coerce reitit query arguments, but not quite getting the results I'm expecting.

plexus14:02:28

route:

["/paginated/:table"
 {:get {:coercion reitit.coercion.malli/coercion
        :parameters {:path {:table string?}
                     :query {:page int?
                             :page-size int?}}
        :responses {200 {:body any?}}
        :handler (fn [{:keys [path-params params] :as req}]
                   {:status 200
                    :body "OK"})}}]

plexus14:02:00

and router

plexus14:02:03

(http/router routes {:data {:middleware [rrc/coerce-exceptions-middleware
                                         rrc/coerce-request-middleware
                                         rrc/coerce-response-middleware]}})

plexus14:02:36

is it reasonable to expect that this way a ?page=1 parameter will come in as {:query-params {"page" 1}}?

ikitommi14:02:00

yes, but the coerced params are under :parameters, so_

{:parameters {:query {{:page 1}}}

ikitommi14:02:44

{:table string?} --> [:map [:table string?]]

plexus14:02:44

and not {"page" "1"}

plexus14:02:06

ahhh thanks, let me try that

plexus14:02:26

same for :query then I suppose? [:map {:page ...}]

ikitommi14:02:42

didn’t add any sugar for the top-level maps in the malli coercion, so, all parameters need to be defined in the malli syntax.

ikitommi14:02:08

crossed my mind to do that, would be 1:1 to switch the simple cases from data-specs to malli…

plexus14:02:39

still getting string...

:parameters {:path [:map
                               [:table string?]]
                        :query [:map
                                [:page int?]
                                [:page-size int?]]}

ikitommi14:02:12

["/math"
        {:swagger {:tags ["math"]}}

        ["/plus"
         {:get {:summary "plus with malli query parameters"
                :parameters {:query [:map [:x int?] [:y int?]]}
                :responses {200 {:body [:map [:total int?]]}}
                :handler (fn [{{{:keys [x y]} :query} :parameters}]
                           {:status 200
                            :body {:total (+ x y)}})}
          :post {:summary "plus with malli body parameters"
                 :parameters {:body [:map [:x int?] [:y int?]]}
                 :responses {200 {:body [:map [:total int?]]}}
                 :handler (fn [{{{:keys [x y]} :body} :parameters}]
                            {:status 200
                             :body {:total (+ x y)}})}}]]]

plexus14:02:33

sorry, had some connectivity issues there. Let me have a look at the example.

plexus14:02:30

I guess you use all the reitit.*.middleware instead of ring-defaults now

ikitommi14:02:27

there are useful middeware there, but for example the site is really heavy and many things are solved alread (like fallback to look up file resources)

plexus14:02:44

ok, progress. changed reitit.http/router to reitit.ring/router, and now I'm getting a 406 Not Acceptable

ikitommi14:02:56

there is a bug somewhere in the malli-coercion, bumped into it few days ago, if the response is not valid, gives really weird error. will fix that soon.

plexus14:02:58

added Accept-Encoding=application/json and now it's a 405 Method Not Allowed

plexus14:02:04

still going to consider this progress 🙂

ikitommi14:02:54

I have a minimal shadow-cljs + deps + reitit + malli example project, will good defaults, try to push that out before ClojureD

plexus14:02:11

that'd be great, thanks!

plexus14:02:35

was hoping not to manually have to parse those integers but maybe the trouble isn't worth it right now

ikitommi14:02:45

default reitit has no opinions, lot’s of things that need to decided, not the friendliest for new users

ikitommi14:02:26

if you test the sample app I pointed and just strip away things?

ikitommi14:02:45

there is also the request/response printer, just commented out.

ikitommi14:02:59

let’s you see what the different mw do in the chain.

ikitommi14:02:23

uses deep-diff btw 😉

plexus14:02:44

nice, probably why it's been doing so well. most downloaded lambdaisland project 🙂

plexus14:02:42

we're working on clojurescript support for deep-diff BTW

👍 8
steveb8n21:02:38

@U07FP7QJ0 this is great news. when using Shadow-cljs and Cursive, there’s basically no easy visual diff tooling that I can find. cljs support in deep-diff will fill this gap perfectly

plexus15:02:32

oh it's working now!

plexus15:02:00

misunderstood something about httpie :face_palm::skin-tone-2:

ikitommi15:02:24

It looks like malli need support for sequence schemas. I just need the simplest “varargs” case, why not do the whole thing while at it.

ikitommi15:02:47

I’m thinking of re-using the map/multi syntax:

(m/valid?
  [:cat
   [:x int?]
   [:y int?]
   [:rest [:* string?]]]
  [1 2 "kikka" "kukka"])
; => true

ikitommi15:02:26

if https://github.com/cgrand/seqexp would be ported to cljs, could use that behind the scenes.

ikitommi15:02:56

like clojure.spec.alpha/conform, there could be something for that?

(m/destructure
  [:cat
   [:x int?]
   [:y int?]
   [:rest [:* string?]]]
  [1 2 "kikka" "kukka"])
; {:x 1
;  :y 2
;  :rest ("kikka "kukka")}

ikitommi15:02:47

without that, this would be just enough:

(m/valid?
  [:cat int? int? [:* string?]]
  [1 2 "kikka" "kukka"])
; => true

ikitommi15:02:55

ideas most welcome on this.

ikitommi15:02:39

(require '[net.cgrand.seqexp :as se])

(se/exec
  (se/cat
    (se/as :x int?)
    (se/as :y int?)
    (se/as :restz (se/* :string?)))
  [1 2 "kikka" "kukka"])
;{:x (1)
; :y (2)
; :restz ("kikka" "kukka")
; :rest ()}

borkdude19:02:34

@ikitommi fwiw, I ported clojure spec to a graalvm compatible subset here: https://github.com/borkdude/spartan.spec

borkdude19:02:53

it contains all the regex ops

ikitommi19:02:59

oh, nice. Will have a look.

borkdude19:02:35

it's basically a copy with things removed

ikitommi19:02:39

What if explainer would also report succesfully validated values in same format as errors? At top level, the m/explain it would return either errors or succefull values.

ikitommi19:02:56

it would give destructuring basically for free.

ikitommi19:02:47

now, only errors are pushed to the accumulator. It could be swapped Into a protocol, which has -explain-error and -explain-success functions. Schemas write to those and internally, in case of first error, it just tosses away the successes and collects only errors. In case of no errors, it would return the successes in the same explain -format!!

ikitommi19:02:12

as it contains both the paths to schemas and in data, one can "unexplain" in a generic way

ikitommi19:02:33

Spec has conform, unform and explain , malli could only have explain for all these, but has validate for perf reasons. Still, less for more.

borkdude20:02:18

in spec the bulk of the work is done in conform. this result is fed into explain which transforms that result to something readable, but even valid? uses conform

ikitommi09:02:38

I don’t think result of s/conform is fed into explain. It either returns the conformed value of ::s/invalid. In case of latter, s/explain is called again with the original data. Which is fine, as it’s the failure path as you said.

ikitommi09:02:04

m/validate does much less than s/valid? and because of that - is much faster.

ikitommi09:02:12

or, actually, m/validator.

ikitommi09:02:38

;; 40ns
(let [spec (s/and int? (s/or :pos-int pos-int? :neg-int neg-int?))
      valid? (partial s/valid? spec)]
  (cc/quick-bench
    (valid? 0)))

;; 5ns
(let [valid? (m/validator [:and int? [:or pos-int? neg-int?]])]
  (cc/quick-bench
    (valid? 0)))

borkdude20:02:57

I think might have the philosophy that when something is valid it should go fast, but when something is invalid, it doesn't matter if you have to call conform again

☝️ 4
borkdude20:02:03

because something is wrong anyway