yada

kenny 2019-08-23T00:18:24.051900Z

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?

kenny 2019-08-23T15:07:26.053600Z

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?

kenny 2019-08-23T15:08:59.053900Z

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?

kenny 2019-08-23T15:20:05.054100Z

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?

dominicm 2019-08-23T15:21:48.054300Z

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)

kenny 2019-08-23T15:23:26.054500Z

Hmm, I see. And the proper way to do that is by assoc'ing the :response?

dominicm 2019-08-23T15:24:04.054900Z

Yeah, or merge

kenny 2019-08-23T15:25:32.055600Z

Gotcha. What are the functions in the :responses map doing? "Declared responses" https://juxt.pro/yada/manual/index.html#declared-responses

kenny 2019-08-23T15:44:17.064100Z

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

dominicm 2019-08-23T15:49:45.064300Z

It goes at the top level, not under :methods, but as a sibling to it

kenny 2019-08-23T15:51:01.065100Z

Gotcha. Is there a table of where all these properties go and what they mean?

dominicm 2019-08-23T15:52:14.065300Z

Probably not. It would be a good addition. I've picked this up from sitting in the JUXT office for years :)

kenny 2019-08-23T16:09:05.065500Z

By doing (update ctx :response merge {:status 400 :body "bad"}), I get the whole Yada context as the :body.

kenny 2019-08-23T16:11:03.065700Z

Oh, you return just the response, not the whole ctx.

kenny 2019-08-23T16:11:32.065900Z

Interesting that it overrides the :responses handler.

kenny 2019-08-23T16:14:22.066100Z

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.

dominicm 2019-08-23T16:15:05.066300Z

4 tripped me up recently, didn't expect it. You've analysed it quicker than I did.

kenny 2019-08-23T16:16:02.066500Z

Any guidance on choosing which one to use? Just starting out, it's not very clear what the tradeoffs are :thinking_face:

dominicm 2019-08-23T16:19:33.067200Z

@malcolmsparks might advise best. Although I mostly see response merging.

kenny 2019-08-23T16:22:29.067400Z

Ok cool. If you are doing response merging, is there a reason to use the :responses feature?

dominicm 2019-08-23T16:42:08.068400Z

It could still be useful, if it's available. To use shared 400 formatting.

jaihindhreddy 2019-08-23T04:41:26.052400Z

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

jaihindhreddy 2019-08-23T04:42:29.052600Z

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

mccraigmccraig 2019-08-23T06:36:23.052900Z

[1] (assoc (yada.context/-&gt;Response) :status 403)

dominicm 2019-08-23T06:40:49.053200Z

I think you should use the :response in the ctx @mccraigmccraig, it might be pre populated

mccraigmccraig 2019-08-23T06:42:01.053400Z

ha, quite possibly @dominicm - we've been using yada quite a long time now, and some of our usage is probably archaic

kenny 2019-08-23T15:07:26.053600Z

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?

kenny 2019-08-23T15:08:59.053900Z

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?

kenny 2019-08-23T15:20:05.054100Z

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?

dominicm 2019-08-23T15:21:48.054300Z

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)

kenny 2019-08-23T15:23:26.054500Z

Hmm, I see. And the proper way to do that is by assoc'ing the :response?

dominicm 2019-08-23T15:24:04.054900Z

Yeah, or merge

kenny 2019-08-23T15:24:28.055500Z

Does Yada have any integration with clojure.spec? We have our entire data model written in Spec. It looks like Yada only uses Schema.

kenny 2019-08-23T15:25:32.055600Z

Gotcha. What are the functions in the :responses map doing? "Declared responses" https://juxt.pro/yada/manual/index.html#declared-responses

dominicm 2019-08-23T15:26:39.056200Z

No. Spec is unsuitable for external web boundaries.

kenny 2019-08-23T15:27:16.056400Z

Why?

dominicm 2019-08-23T15:30:05.058500Z

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.

dominicm 2019-08-23T15:30:41.059200Z

On top of that, you usually want to restrict keys in the web context as you're going to pass them into the database.

kenny 2019-08-23T15:31:05.059600Z

I see. Can't that be avoided with select-keys?

dominicm 2019-08-23T15:31:47.060700Z

More complicated than that because of nested maps, and lists of maps.

kenny 2019-08-23T15:33:14.061700Z

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?

dominicm 2019-08-23T15:39:20.063500Z

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.

kenny 2019-08-23T15:41:27.064Z

Makes sense. Is it extensible now?

kenny 2019-08-23T15:44:17.064100Z

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

dominicm 2019-08-23T15:49:45.064300Z

It goes at the top level, not under :methods, but as a sibling to it

dominicm 2019-08-23T15:50:08.065Z

Not particularly. It should be. I don't think it would be a huge task to make it so.

kenny 2019-08-23T15:51:01.065100Z

Gotcha. Is there a table of where all these properties go and what they mean?

dominicm 2019-08-23T15:52:14.065300Z

Probably not. It would be a good addition. I've picked this up from sitting in the JUXT office for years :)

kenny 2019-08-23T16:09:05.065500Z

By doing (update ctx :response merge {:status 400 :body "bad"}), I get the whole Yada context as the :body.

kenny 2019-08-23T16:11:03.065700Z

Oh, you return just the response, not the whole ctx.

kenny 2019-08-23T16:11:32.065900Z

Interesting that it overrides the :responses handler.

kenny 2019-08-23T16:14:22.066100Z

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.

dominicm 2019-08-23T16:15:05.066300Z

4 tripped me up recently, didn't expect it. You've analysed it quicker than I did.

kenny 2019-08-23T16:16:02.066500Z

Any guidance on choosing which one to use? Just starting out, it's not very clear what the tradeoffs are :thinking_face:

kenny 2019-08-23T16:17:28.067100Z

If I start with yada/lean, how would I add in the body coercion for Transit and JSON?

dominicm 2019-08-23T16:19:33.067200Z

@malcolmsparks might advise best. Although I mostly see response merging.

kenny 2019-08-23T16:22:29.067400Z

Ok cool. If you are doing response merging, is there a reason to use the :responses feature?

kenny 2019-08-23T16:29:16.068200Z

If you specify body parameter schema, is it assumed that the body is read in as json/edn?

dominicm 2019-08-23T16:42:08.068400Z

It could still be useful, if it's available. To use shared 400 formatting.

dominicm 2019-08-23T16:50:48.068800Z

No, it works for form data too