oliyh / martian

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

martian-hato puts implementation objects in the request that martian-vcr can write but not read back #203

Closed lvh closed 3 weeks ago

lvh commented 1 month ago

When using martian-hato with martian-vcr (and pretty printing on, FWIW, see vcr opts below):

(def vcr-opts
  {:store
   {:kind :file
    :root-dir "test-resources/REDACTED-vcr"
    :pprint? true}
   :on-missing-response :throw-error})

This is used like this:

(defn recording-bootstrap
  "A variant of `bootstrap` that saves responses with vcr."
  []
  (let [record (martian-vcr/record vcr-opts)]
    (li/bootstrap
     {:opts-hook
      (fn [opts]
        (li/inject opts record :before (:name martian-http/perform-request)))})))

(defn playbacking-bootstrap
  "A variant of `bootstrap` that returns previously-saved results using vcr."
  []
  (let [playback (martian-vcr/playback vcr-opts)]
    (li/bootstrap
     {:opts-hook
      (fn [opts]
        (li/inject opts playback :replace (:name martian-http/perform-request)))})))

Just to clarify the require: 1[martian.hato :as martian-http]`

The li/bootstrap referred to is:

(defn bootstrap
  "A custom version of [[martian.hato/bootstrap-openapi]] that adds an
   authentication ([[add-authn]]) via interceptor.

  Optionally takes a map with the following optional arguments:

  * `opts-hook` gets called with the martian bootstrap opts right before they're
  passed to bootstrap, allowing you to customize interceptors (for example). If
  you're using this there's a good chance you want to look at [[inject]]."
  ([] (bootstrap nil))
  ([{:keys [opts-hook] :or {opts-hook identity}}]
   (let [definition (load-api-definition!)
         base-url (openapi/base-url openapi-url nil definition)
         opts (-> martian-http/default-opts
                  (inject add-authn :before (:name martian-http/perform-request))
                  opts-hook)]
     (martian/bootstrap-openapi base-url definition opts))))

Free free to use that a feature suggestion if it's helpful :) Happy to provide inject too, but I'm sure you can guess the implementation: it's essentially a version of inject that works on opts instead of on the list of interceptors directly:

(defn inject
  "Like [[int/inject]] for interceptors, but acts on the martian `opts` map that
  _contains_ the interceptors."
  [opts & args]
  (apply update opts :interceptors int/inject args))

The resulting saved EDN file looks like this:

{:request-time 756,
 :request {:user-info nil,
           :as :text,
           :headers {"Accept" "application/json"},
           :url "https://REDACTED",
           :http-request #object[jdk.internal.net.http.HttpRequestImpl "0x2befd612" "REDACTED"]
           ;; ... irrelevant keys redacted
           :request-method :get},
 :http-client #object[jdk.internal.net.http.HttpClientFacade
                      "0x2e234aeb"
                      "jdk.internal.net.http.HttpClientImpl@1a162bb8(7)"],
 :headers {"content-encoding" "gzip",
           ;; irrelevant keys redacted
           "report-to" "REDACTED"},
 :status 200,
 :content-type :application/json,
 :uri "REDACTED",
 :content-type-params {},
 :version :http-1.1,
 :body "REDACTED"}

The write succeeds but the read never will because the #object reader tag can't actually be deserialized.

I think I can work around this with a carefully placed interceptor to go along with the recording one.