oliyh / martian

The HTTP abstraction library for Clojure/script, supporting OpenAPI, Swagger, Schema, re-frame and more
MIT License
529 stars 44 forks source link

How to choose between produced or consumed content-types? #117

Closed onetom closed 3 years ago

onetom commented 3 years ago

How can I pick a specific content type, if there are multiple content-types specified for the :produces / :consumes options?

The following code requests EDN as the content type of the response, regardless of the order of the 2 types in the :produces vector:

  (-> (martian-http/bootstrap "" [{:route-name :x
                                   :method     :get
                                   :produces   ["application/json"
                                                "application/edn"]}])
      (martian/request-for :x))

It responds with:

=> {:method :get, :url "", :as :text, :headers {"Accept" "application/edn"}}

Since the order of that vector doesn't matter, I highly suspect there is a way to specify which content type to use.

The documentation mentions that I can more directly control the request body with ::martian/body, so I was hoping for a similar escape hatch.

Alternatively, it would be nice if there would be a way to hack the value returned by martian/request-for and feed it into something, which performs the request. Right now, there is a lot of code duplication between request-for and response-for, so it's unclear how could they be more composable...

It's a bit hacky approach, but I can imagine request-for not just returning the request, but also a function in it's meta-data, which can perform the request. So instead of just removing the interceptor stack with tc/terminate, we can capture the remaining steps and construct a function, which executes them.

Something like:

(-> api (martian/request-for :x) (assoc-in [:headers "accept"] "blah") (martian/perform))

where martian/perform can do something like (-> req meta :ctx tc/execute)?

(I'm not very experienced with interceptors, so I'm not sure about this part. Also, I've only studied https://github.com/metosin/sieppari closer.)

oliyh commented 3 years ago

Hello,

Martian "prefers" transit > edn > json, so will choose in that order, which is defined here: https://github.com/oliyh/martian/blob/master/core/src/martian/encoders.cljc#L40-L52

The value returned by request-for is the result of all the enter branches on the entire interceptor stack, so this map is suitable for passing directly into your http library of choice, e.g.

(clj-http.client/request (martian/request-for m :x))

You won't get the leave stack though in this case. For your requirement of different content-type behaviour, you can just replace the coerce-response interceptor with one of your own that has your behaviour:

(def my-coerce-response
  {:name ::my-coerce-response
   :enter (fn [ctx}
               (assoc-in ctx [:request :headers "Accept"] "application/foo"))
   :leave (fn [ctx]
               (update-in ctx [:response :body] decode-foo))})

(martian-http/bootstrap "" 
                        [...routes...] 
                        {:interceptors (-> martian-http/default-interceptors
                                           (martian.interceptors/inject my-coerce-response :replace martian.interceptors/coerce-response))})

Hope this helps