Skip to content

Commit

Permalink
Use a multimethod for content-type dispatching
Browse files Browse the repository at this point in the history
Make the content-type middleware that handles inlining of File/URL/Image
extensible through a multimethod. This makes it possible for developers to add
handling of custom types.

Regular Clojure values can be given a `:type` metadata to make them
distinguishable. Other types can dispatch on the class.
  • Loading branch information
plexus committed Oct 10, 2018
1 parent b2c0b92 commit f5f9023
Showing 1 changed file with 41 additions and 39 deletions.
80 changes: 41 additions & 39 deletions src/cider/nrepl/middleware/content_type.clj
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@
[2] https://github.com/technomancy/nrepl-discover/blob/master/src/nrepl/discover/samples.clj#L135
[3] https://tools.ietf.org/html/rfc2045
[4] https://tools.ietf.org/html/rfc2017"
{:authors ["Reid 'arrdem' McKenzie <[email protected]>"]}
{:authors ["Reid 'arrdem' McKenzie <[email protected]>"
"Arne 'plexus' Brasseur <[email protected]>"]}
(:require [cider.nrepl.middleware.slurp :refer [slurp-reply]])
(:import java.awt.Image
[java.io ByteArrayOutputStream File OutputStream]
Expand Down Expand Up @@ -78,43 +79,44 @@
(as-url [^URL u]
u))

(defn response+content-type
"Consumes an nREPL response, having a `:value`. If the `:value` is
recognized as an AWT Image, a File, or a File URI, rewrite the
response to have a `:content-type` being a MIME type of the content,
and a `:body` to re-use the RFC term for the message payload."
[{:keys [session value] :as response}]
(cond
;; FIXME (arrdem 2018-04-03):
;;
;; This could be more generic in terms of tolerating more
;; protocols / schemes

;; RFC-2017 external-body responses for UR[IL]s and things which are just wrappers thereof
(or (and (instance? File value)
(.exists ^File value))
(instance? URI value)
(instance? URL value))
(assoc response
:content-type ["message/external-body"
{"access-type" "URL"
"URL" (.toString (as-url value))}]
:body "")

;; FIXME (arrdem 2018-04-03):
;;
;; This is super snowflakey in terms of only supporting base64
;; coding this one kind of object. This could definitely be
;; more generic / open to extension but hey at least it's
;; re-using machinery.

(instance? java.awt.Image value)
(with-open [bos (ByteArrayOutputStream.)]
(ImageIO/write ^Image value "png" ^OutputStream bos)
(merge response
(slurp-reply ["image/png" {}] (.toByteArray bos))))

:else response))
(defn external-body-response
"Partial response map having an external-body content-type referring to the given URL.
See RFC-2017: Definition of the URL MIME External-Body Access-Type."
[value]
{:content-type ["message/external-body"
{"access-type" "URL"
"URL" (.toString (as-url value))}]
:body ""})

(defmulti content-type-response
"Consumes an nREPL response, having a `:value`. If the `:value` is of a
recognized type, then rewrite the response to have a `:content-type` being a
MIME type of the content, and a `:body` to re-use the RFC term for the message
payload.
Dispatches on the [[clojure.core/type]] of the value, i.e. the metadata
`:type` value, or the class."
(comp type :value))

(defmethod content-type-response :default [response]
response)

(defmethod content-type-response URI [{:keys [value] :as response}]
(merge response (external-body-response value)))

(defmethod content-type-response URL [{:keys [value] :as response}]
(merge response (external-body-response value)))

(defmethod content-type-response File [{^File file :value :as response}]
(if (.exists file)
(merge response (external-body-response file))
response))

(defmethod content-type-response java.awt.Image [{^java.awt.Image image :value :as response}]
(with-open [bos (ByteArrayOutputStream.)]
(ImageIO/write image "png" ^OutputStream bos)
(merge response (slurp-reply ["image/png" {}] (.toByteArray bos)))))

(defn content-type-transport
"Transport proxy which allows this middleware to intercept responses
Expand All @@ -126,7 +128,7 @@
(recv [this timeout]
(.recv transport timeout))
(send [this response]
(.send transport (response+content-type response)))))
(.send transport (content-type-response response)))))

(defn handle-content-type
"Handler for inspecting the results of the `eval` op, attempting to
Expand Down

0 comments on commit f5f9023

Please sign in to comment.