Fork me on GitHub
#reitit
<
2021-05-30
>
nickik17:05:34

Hallo. Playing around with this. I don't quite understand what the right way is to do validation of my endpoints and error handling, I want to use malli.

nickik17:05:36

((ring/ring-handler (ring/router ["/question" {:post {:handler (fn [_] {:status 200, :body "ok"})}}]))   {:request-method :post, :uri "/question" :body "2"})

nickik17:05:08

So how would I validate that the body that is being send is according to some malli schema.

nickik17:05:32

Or say convert the "2" to 2.

dharrigan17:05:19

Hi @nickik, here is a repo that you can use for some learning, . You'll notice here I define some routes

dharrigan17:05:33

and here is where I validate the input to said routes

dharrigan17:05:45

The repo is only an example, others may have better ways 🙂

dharrigan17:05:31

(`https://github.com/dharrigan/startrek/blob/master/src/startrek/base/api/starship/routes.clj#L71) is the post and at line 72 is the spec to the route, for validating the post

nickik17:05:16

Intersting, this is basically what I am trying but I doesn't seem to work.

nickik17:05:17

(def router2 (ring/router ["/question" {:post {:handler (fn [_] {:status 200, :body "ok"})
                                               :parameters {:body [:vector :int]}}}]))
(def app2 (ring/ring-handler router2))
(app2 {:request-method :post, :uri "/question" :body-params "test"})

nickik17:05:27

@dharrigan Seems to me this should produce an error

nickik17:05:19

Or maybe:

(def router2 (ring/router ["/question" {:post {:handler (fn [_] {:status 200, :body "ok"})
                                               :coercion reitit.coercion.malli/coercion
                                               :parameters {:body [:vector :int]}}}]
                          {:compile coercion/compile-request-coercers}))
(def app2 (ring/ring-handler router2))
(app2 {:request-method :post, :uri "/question" :body-params "test"})

dharrigan17:05:59

you seem to have :int, not int?

nickik17:05:16

:int is a valid malli schema

nickik17:05:53

Doesn't change the outcome

dharrigan18:05:25

["/question" {:post {:handler (fn [request]
                                     (prn request)
                                     {:status 200 :body (-> request :body-params :question)})
                          :parameters {:body [:map
                                              [:question [:vector int?]]]}}}]]

dharrigan18:05:31

❯ http --verbose :8080/question question:='[1, 2]'
POST /question HTTP/1.1
Accept: application/json, */*;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: 20
Content-Type: application/json
Host: localhost:8080
User-Agent: HTTPie/2.4.0

{
    "question": [
        1,
        2
    ]
}


HTTP/1.1 200 OK
Content-Length: 5
Content-Type: application/json;charset=utf-8
Date: Sun, 30 May 2021 18:01:17 GMT
Server: Jetty(9.4.40.v20210413)

[
    1,
    2
]

nickik18:05:38

So what if you pass in a string instead of an int

dharrigan18:05:10

I'm passing in numbers

nickik18:05:32

Sure but what happens if you pass a wrong input, I would expect it to throw an error

dharrigan18:05:00

You mean like this?

dharrigan18:05:03

❯ http --verbose :8080/question question:='[1, "david"]'
POST /question HTTP/1.1
Accept: application/json, */*;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: 26
Content-Type: application/json
Host: localhost:8080
User-Agent: HTTPie/2.4.0

{
    "question": [
        1,
        "david"
    ]
}


HTTP/1.1 500 Server Error
Cache-Control: must-revalidate,no-cache,no-store
Connection: close
Content-Length: 13635
Content-Type: application/json
Server: Jetty(9.4.40.v20210413)

{
    "cause0": "clojure.lang.ExceptionInfo: Request coercion failed: #reitit.coercion.CoercionError{:schema [:map {:closed true} [:question [:vector int?]]], :value {:question [1 &quot;david&quot;]}, :errors (#Error{:path [:question 0], :in [:question 1], :schema int?,

dharrigan18:05:12

notice I changed the number 2 to a string

dharrigan18:05:28

you would have to of course, put some exception handling in place, but it looks to me it's doing the right thing.

dharrigan18:05:29

If you put in the default exception handlers, you'll get this:

dharrigan18:05:31

"question": [
        1,
        "david"
    ]
}


HTTP/1.1 400 Bad Request
Content-Length: 355
Content-Type: application/json;charset=utf-8
Date: Sun, 30 May 2021 18:07:16 GMT
Server: Jetty(9.4.40.v20210413)

{
    "coercion": "malli",
    "errors": [
        {
            "in": [
                "question",
                1
            ],
            "message": "should be an int",
            "path": [
                "question",
                0
            ],
            "schema": "int?",
            "value": "david"
        }
    ],
    "humanized": {
        "question": [
            null,
            [
                "should be an int"
            ]
        ]
    },
    "in": [
        "request",
        "body-params"
    ],
    "schema": "[:map {:closed true} [:question [:vector int?]]]",
    "type": "reitit.coercion/request-coercion",
    "value": {
        "question": [
            1,
            "david"
        ]
    }
}

nickik18:05:43

So maybe it only works on map, what if you remove the [:map ...] and only put \:int

dharrigan18:05:02

Try it, I have to go now to put kid to bed 🙂

nickik18:05:07

Can you also do this in app like so:

(app2 {:request-method :post, :uri "/question" :body-params [1]})

nickik18:05:24

Ok, I think Ill figure it out from here.

dharrigan18:05:15

["/question" {:post {:handler (fn [request]
                                     {:status 200 :body (-> request :body-params)})
                          :parameters {:body [:vector int?]}}}]]

dharrigan18:05:40

❯ echo '[1, "david"]' | http --verbose :8080/question
POST /question HTTP/1.1
Accept: application/json, */*;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: 13
Content-Type: application/json
Host: localhost:8080
User-Agent: HTTPie/2.4.0

[
    1,
    "david"
]


HTTP/1.1 400 Bad Request
Content-Length: 273
Content-Type: application/json;charset=utf-8
Date: Sun, 30 May 2021 18:32:32 GMT
Server: Jetty(9.4.40.v20210413)

{
    "coercion": "malli",
    "errors": [
        {
            "in": [
                1
            ],
            "message": "should be an int",
            "path": [
                0
            ],
            "schema": "int?",
            "value": "david"
        }
    ],
    "humanized": [
        null,
        [
            "should be an int"
        ]
    ],
    "in": [
        "request",
        "body-params"
    ],
    "schema": "[:vector int?]",
    "type": "reitit.coercion/request-coercion",
    "value": [
        1,
        "david"
    ]
}

dharrigan18:05:44

hope that helps!

nickik18:05:21

@dharrigan Maybe I'm stupid. Can you send me the full config for the routes?

nickik18:05:33

(ring/ring-handler
  (ring/router ["/question" {:post {:handler (fn [request]
                                               (prn request)
                                               {:status 200 :body (-> request :body-params :question)})
                                    :parameters {:body [:map
                                                        [:question [:vector int?]]]}}}]
               {:data {:compile coercion/compile-request-coercers}} ))

nickik18:05:15

I don't understand what configuration is required. Your projects seems to do this: {:data {:coercion rcm/coercion{:validate rs/validate}}

nickik18:05:14

But somehow for me this doesn't work. In your case you don't seem to have the 'compile' but the documentation here: https://cljdoc.org/d/metosin/reitit/0.5.13/doc/coercion/malli

dharrigan18:05:15

I put it on the branch here:

nickik18:05:07

Mh its funny, I had it working for a minute without all that stuff, so the :muuntaja and the :middleware is required? Whats the minimal configuration?

nickik19:05:32

Ok, so basically my issue was that I didn't understand that the middleware is required to get it to work within ring. The documentation on the malli part was only for the route matching.

nickik19:05:35

@dharrigan You seem to be using clip. Do you like it? I have been using integrant/duct and trying to decide to switch to clip.

dharrigan19:05:06

I like clip a lot. I've been through integrant (tried a bit of duct, was turn off by it) and component and mount

dharrigan19:05:18

clip is very good. I use it at work for big systems, never failed.

nickik19:05:07

I like the idea of duct, having some sort of higher level module system would be nice, but not sure the implementation make is all that practical. It would be good for the clojure community to have an easy starter framework that can do a lot with very little but is still flexible.

nickik19:05:08

I guess I will try clip for this current project, it does seem really cool.

nickik19:05:00

Thanks for your help!