reitit

https://cljdoc.org/d/metosin/reitit/ https://github.com/metosin/reitit/
2021-05-30T17:23:34.047400Z

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.

2021-05-30T17:23:36.047600Z

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

2021-05-30T17:24:08.048300Z

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

2021-05-30T17:26:32.048600Z

Or say convert the "2" to 2.

dharrigan 2021-05-30T17:39:19.049200Z

Hi @nickik, here is a repo that you can use for some learning, <https://github.com/dharrigan/startrek>. You'll notice here <https://github.com/dharrigan/startrek/blob/master/src/startrek/base/api/starship/routes.clj> I define some routes

dharrigan 2021-05-30T17:39:33.049500Z

and here is where I validate the input to said routes <https://github.com/dharrigan/startrek/blob/master/src/startrek/base/api/starship/specs.clj>

dharrigan 2021-05-30T17:39:45.049900Z

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

dharrigan 2021-05-30T17:40:31.050500Z

(`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

dharrigan 2021-05-30T17:40:32.050700Z

.

2021-05-30T17:47:16.051Z

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

2021-05-30T17:47:17.051200Z

(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"})

2021-05-30T17:47:27.051400Z

@dharrigan Seems to me this should produce an error

2021-05-30T17:53:19.051700Z

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"})

2021-05-30T17:53:37.051900Z

According to: https://cljdoc.org/d/metosin/reitit/0.5.13/doc/coercion/malli

dharrigan 2021-05-30T17:53:59.052200Z

you seem to have :int, not int?

2021-05-30T17:58:16.052500Z

:int is a valid malli schema

2021-05-30T17:58:53.052900Z

Doesn't change the outcome

dharrigan 2021-05-30T18:01:25.053100Z

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

dharrigan 2021-05-30T18:01:31.053300Z

❯ 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
]

2021-05-30T18:03:38.053600Z

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

dharrigan 2021-05-30T18:04:04.053800Z

Nope

dharrigan 2021-05-30T18:04:10.054Z

I'm passing in numbers

2021-05-30T18:04:32.054500Z

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

dharrigan 2021-05-30T18:05:00.054700Z

You mean like this?

dharrigan 2021-05-30T18:05:03.055Z

❯ 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 &amp;quot;david&amp;quot;]}, :errors (#Error{:path [:question 0], :in [:question 1], :schema int?,

dharrigan 2021-05-30T18:05:12.055300Z

notice I changed the number 2 to a string

dharrigan 2021-05-30T18:05:28.055700Z

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

dharrigan 2021-05-30T18:07:29.056800Z

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

dharrigan 2021-05-30T18:07:31.057200Z

"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"
        ]
    }
}

2021-05-30T18:07:43.057500Z

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

dharrigan 2021-05-30T18:08:02.058Z

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

2021-05-30T18:08:07.058200Z

Can you also do this in app like so:

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

2021-05-30T18:08:24.058600Z

Ok, I think Ill figure it out from here.

dharrigan 2021-05-30T18:32:12.058800Z

back

dharrigan 2021-05-30T18:32:15.059Z

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

dharrigan 2021-05-30T18:32:21.059200Z

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

[
    1,
    2
]


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

[
    1,
    2
]

dharrigan 2021-05-30T18:32:40.059400Z

❯ 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"
    ]
}

dharrigan 2021-05-30T18:32:44.059600Z

hope that helps!

dharrigan 2021-05-30T18:32:44.059800Z

.

2021-05-30T18:49:21.060300Z

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

2021-05-30T18:49:33.060500Z

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

2021-05-30T18:50:15.061Z

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

2021-05-30T18:51:14.061900Z

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

dharrigan 2021-05-30T18:51:15.062Z

I put it on the branch here:

dharrigan 2021-05-30T18:51:16.062300Z

https://github.com/dharrigan/strip-nils/tree/question

dharrigan 2021-05-30T18:51:17.062500Z

enjoy!

2021-05-30T18:54:07.063100Z

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?

2021-05-30T19:12:32.064100Z

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.

2021-05-30T19:14:35.065600Z

@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.

dharrigan 2021-05-30T19:15:06.066100Z

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

dharrigan 2021-05-30T19:15:18.066400Z

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

2021-05-30T19:24:07.069300Z

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.

2021-05-30T19:24:08.069400Z

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

dharrigan 2021-05-30T19:27:26.069800Z

:thumbsup:

2021-05-30T19:39:00.070100Z

Thanks for your help!