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.
((ring/ring-handler (ring/router ["/question" {:post {:handler (fn [_] {:status 200, :body "ok"})}}])) {:request-method :post, :uri "/question" :body "2"})
So how would I validate that the body that is being send is according to some malli schema.
Or say convert the "2" to 2.
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
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>
The repo is only an example, others may have better ways 🙂
(`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
.
Intersting, this is basically what I am trying but I doesn't seem to work.
(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"})
@dharrigan Seems to me this should produce an error
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"})
According to: https://cljdoc.org/d/metosin/reitit/0.5.13/doc/coercion/malli
you seem to have :int
, not int?
:int is a valid malli schema
Doesn't change the outcome
["/question" {:post {:handler (fn [request]
(prn request)
{:status 200 :body (-> request :body-params :question)})
:parameters {:body [:map
[:question [:vector int?]]]}}}]]
❯ 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
]
So what if you pass in a string instead of an int
Nope
I'm passing in numbers
Sure but what happens if you pass a wrong input, I would expect it to throw an error
You mean like this?
❯ 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?,
notice I changed the number 2 to a string
you would have to of course, put some exception handling in place, but it looks to me it's doing the right thing.
If you put in the default exception handlers, you'll get this:
"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"
]
}
}
So maybe it only works on map, what if you remove the [:map ...] and only put \:int
Try it, I have to go now to put kid to bed 🙂
Can you also do this in app like so:
(app2 {:request-method :post, :uri "/question" :body-params [1]})
Ok, I think Ill figure it out from here.
back
["/question" {:post {:handler (fn [request]
{:status 200 :body (-> request :body-params)})
:parameters {:body [:vector int?]}}}]]
❯ 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
]
❯ 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"
]
}
hope that helps!
.
@dharrigan Maybe I'm stupid. Can you send me the full config for the routes?
(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}} ))
I don't understand what configuration is required. Your projects seems to do this: {:data {:coercion rcm/coercion{:validate rs/validate}}
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
I put it on the branch here:
enjoy!
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?
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.
@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.
I like clip a lot. I've been through integrant (tried a bit of duct, was turn off by it) and component and mount
clip is very good. I use it at work for big systems, never failed.
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.
I guess I will try clip for this current project, it does seem really cool.
:thumbsup:
Thanks for your help!