Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature req: Muuntaja/pluggable encoding/decoding support #8

Open
thenonameguy opened this issue Feb 18, 2020 · 6 comments
Open

Feature req: Muuntaja/pluggable encoding/decoding support #8

thenonameguy opened this issue Feb 18, 2020 · 6 comments

Comments

@thenonameguy
Copy link

Hi! We would like to use https://github.com/metosin/muuntaja as we use metosin/jsonista for JSON encoding and decoding. Would it be possibly to switch out the current hard-coded content encoding library choices to a dynamic approach?

Either adopting muuntaja or defining protocols as extension points would be fine.

@gnarroway
Copy link
Owner

I will take a look. Thanks for your interest!

@gnarroway
Copy link
Owner

@thenonameguy The encode/decode already uses a multi method that dispatches on content type. Does providing your own implementation there work for you?

(defmethod hm/coerce-form-params :application/json
  [req]
  "{\"dummy\": 2}")

(hm/coerce-form-params {:content-type "application/json"
                        :form-params {:hello "world"}})

; => "{\"dummy\": 2}"

@thenonameguy
Copy link
Author

Not really:

  1. Multimethods are slower that how Muuntaja implements it (I have perf-sensitive use-case)
  2. It handles more cases than form/query params, like content negotiation, which is hard to express with this interface

@gnarroway
Copy link
Owner

Okay @thenonameguy , my original interpretation was simply that you wanted to replace the Cheshire optional dependency with something you already depended on.

Given hato is middleware based, it is easy to adapt the middleware chain to do what you want, and to exclude irrelevant pieces.

Below is a small example that encodes :form-params you send into the request body, and decodes the response body back into the same body field (i.e. retains the contract that hato uses). Note that as I reused high level muuntaja code, the response/request is backwards (because muuntaja has the perspective of the web server rather than client).

Let me know if this works for you.

(ns muuntaja-client
  (:require [hato.middleware :as hm]
            [hato.client :as hc]
            [muuntaja.middleware :as mm]
            [muuntaja.core :as mc]))

;; Create re-usable client
(def client (hc/build-http-client {}))

;; Hit a json endpoint without a serializer returns a string
(hc/get "https://httpbin.org/json" {:http-client client :as :json})
; => {...:body "{...}"}

;; Create a muuntaja instance
(def my-mun (mc/create mc/default-options))

;; Transform clojure :body into json :body
(defn- muuntaja-request
  [req]
  (mc/format-response my-mun req
                      (assoc req
                        :muuntaja/response (mc/response-format my-mun req)
                        :body (:form-params req))))

;; Transform json :body into clojure :body
(defn- muuntaja-response
  [_ resp]
  (let [r (mc/format-request my-mun (assoc resp :muuntaja/request (mc/request-format my-mun resp)))]
    (-> r 
        (assoc :body (or (:body-params r) (:body r)))
        (dissoc :body-params))))

;; Wrap the transformers in middleware
(defn wrap-format
  [client]
  (fn
    ([req]
     (muuntaja-response req (client (muuntaja-request req))))
    ([req respond raise]
     (client (muuntaja-request req) #(respond (muuntaja-response req %)) raise))))

;; Modify the default middleware chain:
;; - replace wrap-form-params and wrap-outout-coercion with wrap-format (which does input and output)
(def my-middleware [hm/wrap-request-timing
                    hm/wrap-query-params
                    hm/wrap-basic-auth
                    hm/wrap-oauth
                    hm/wrap-user-info
                    hm/wrap-url
                    hm/wrap-decompression

                    wrap-format
                    ;hm/wrap-output-coercion
                    hm/wrap-exceptions
                    hm/wrap-accept
                    hm/wrap-accept-encoding
                    hm/wrap-multipart
                    hm/wrap-content-type

                    ;hm/wrap-form-params
                    hm/wrap-nested-params
                    hm/wrap-method])

;; Create your new client
(def my-request (hm/wrap-request hc/request* my-middleware))

; Add your own convenience methods if you desire
(defn my-get
  [url opts]
  (my-request (merge opts {:url url :method :get})))

;; Now the form-params are sent as json body, and response returned as clojure
(my-get "https://httpbin.org/json" {:http-client client :as :json :form-params {:kikka 42}})
;; => {...:body {}}

@thenonameguy
Copy link
Author

Yep that sound much closer to what I envisioned.
The middleware support fixes the issue's need for me, now the question is does hato want to include this (optional) support like it does for cheshire or we leave this to users to decide.
If you decide the latter, we should get this code checked in to git somewhere so it can be used as library code or documentation.

@chrisbetz
Copy link

Hi, I had to patch muuntaja first and it all took a little while, so I did not see the answers to this thread. However, you might want to have a look at #10 .

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants