We are reworking our backend HTTP services to use Pathom. I am looking into using yada instead of Compojure + http-kit. I'm mostly interested in: interceptors instead of middleware, async w/manifold, and automatic API doc generation. I see a lot of "(coming soon)" in the docs in the sections I'm interested in. Is there some tests, examples, or other code I can look at to see how things are used? I've looked at the phonebook app but it is pretty basic.
Some more specific questions:
1. It's not totally obvious how to simply return a custom status code from a handler. Just returning {:status 400 :body "invalid"}
does not work. The map will get prn
'ed and returned as a string with the status 200. When should you use an explicit response versus declared response?
2. Is there an example of authentication? I've looked at https://juxt.pro/yada/manual/index.html#authentication but it's not clear where things are supposed to go. The Cookie Authentication section just has a map with no info on where it goes, what :verify
is expected to return, etc: https://juxt.pro/yada/manual/index.html#cookie-authentication.
3. Interceptors https://juxt.pro/yada/manual/index.html#interceptors. When should interceptors be used? How do you use them? I expect this is where I would do the typical "additional context" stuff (e.g. attach a customer's db to the context, attach a user-id and customer-id to the context, etc)
4. Custom body coercion. How do implement custom content types for both the request and response?
I'll check out the talk, thanks @jaihindhreddy!
Oh, so it's similar to Pedestal where you update the :response
. This section is still really throwing me off: https://juxt.pro/yada/manual/index.html#status-responses. Are the :responses
for a status called after your handler has returned a matching response?
I saw the piece on assoc
'ing the status onto :response
in the docs. What confused me was this line:
> When you need complete control over the response you should return the request-context's response
This implies that most often you don't need complete control over the response. Which raises the question of why don't I always need control over the response?
Ohh, it looks like you can throw
an ex-info
with :status
:
(throw (ex-info "forbidden" {:status 403}))
and that will set the status. It is a bit strange what it returns though. Given this handler:
(defn eql-handler
[ctx]
(throw (ex-info "forbidden" {:status 403})))
If I run (yada/response-for routes :get "<https://localhost/eql>")
, this is the response:
{:status 403,
:headers {"content-length" "27", "content-type" "text/plain;charset=utf-8"},
:body "forbidden\r\n\r\n{:status 403}\n"}
Why is the ex-info data part of the response?Normally you just return a body, and yada takes care of the rest. Occasionally you need to set a caching header or indicate a reactive change in status (eg you passed schema, but some other logic said you were invalid, so you're invalid)
Hmm, I see. And the proper way to do that is by assoc
'ing the :response
?
Yeah, or merge
Gotcha. What are the functions in the :responses
map doing? "Declared responses" https://juxt.pro/yada/manual/index.html#declared-responses
I'm pretty sure that doc section is out of date. I have tried doing the same thing locally and received a Schema error. This is the piece it doesn't like:
{400 {:description "Malformed request"
:produces #{"text/plain"}
:response (fn [ctx] "bad req")}}
The error being:
{:methods
{:get
{:responses
{400
{(not (namespace :produces)) invalid-key,
(not (namespace :response)) invalid-key}}}}}
It goes at the top level, not under :methods, but as a sibling to it
Gotcha. Is there a table of where all these properties go and what they mean?
Probably not. It would be a good addition. I've picked this up from sitting in the JUXT office for years :)
By doing (update ctx :response merge {:status 400 :body "bad"})
, I get the whole Yada context as the :body
.
Oh, you return just the response, not the whole ctx.
Interesting that it overrides the :responses
handler.
So it seems you have the following options for setting the status on a response:
1. In the "all good" case you return a map and let Yada handle setting the status
2. You throw an ex-info
with :status
in the ex-data and the ex-data
will be the response.
3. You throw an ex-info
with :status
in the ex-data and let the :responses
handler for the given response code handle it
4. You return a Response by merging (:response ctx)
with the status and body you want. This will skip the :responses
handler.
4 tripped me up recently, didn't expect it. You've analysed it quicker than I did.
Any guidance on choosing which one to use? Just starting out, it's not very clear what the tradeoffs are :thinking_face:
@malcolmsparks might advise best. Although I mostly see response merging.
Ok cool. If you are doing response merging, is there a reason to use the :responses
feature?
It could still be useful, if it's available. To use shared 400 formatting.
Didn't use Yada in anger but I believe the philosophy is: implementing HTTP properly is hard. Interceptors are a good abstraction but too primitive for most applications. yada tries to provide a model to "think in resources" instead of lower-level things like interceptors, and it does this by having a default chain of interceptors (which you can override but won't have to most of the time). Instead of writing interceptors, users describe resources as maps with "well-known" keys that yada uses in it's interceptor chain. Relevant part from a talk about yada: https://youtu.be/XUP7e17xqVI?t=426
The talk's more than a couple years old now, so that default interceptor chain might have changed (by way of fixation and/or accretion I hope).
[1] (assoc (yada.context/->Response) :status 403)
I think you should use the :response in the ctx @mccraigmccraig, it might be pre populated
ha, quite possibly @dominicm - we've been using yada quite a long time now, and some of our usage is probably archaic
Does Yada have any integration with clojure.spec? We have our entire data model written in Spec. It looks like Yada only uses Schema.
No. Spec is unsuitable for external web boundaries.
Why?
Because it gives attackers access to all your spec keys. Some of which might be very slow (and not run in production in normal circumstances). This opens you up to DOS attacks, the guidance from Alex on this was to validate data before passing it to spec.
On top of that, you usually want to restrict keys in the web context as you're going to pass them into the database.
I see. Can't that be avoided with select-keys?
More complicated than that because of nested maps, and lists of maps.
Sure. We have also had this problem for storing data in external DBs and have an internal solution to it. But assuming you could handle that case then all would be good?
Yeah. But yada won't add support for spec1 because of that. It lets the user shoot themselves too easily. Security is hard. We do want to make the validation more extensible though.
Makes sense. Is it extensible now?
Not particularly. It should be. I don't think it would be a huge task to make it so.
If I start with yada/lean
, how would I add in the body coercion for Transit and JSON?
If you specify body parameter schema, is it assumed that the body is read in as json/edn?
No, it works for form data too