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

Use a multimethod for content-type dispatching #548

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## master (unreleased)

### New features

* [#548](https://github.com/clojure-emacs/cider-nrepl/pull/548): Make the content-type middleware extensible via multimethod

## 0.27.4 (2021-12-15)

### Bugs fixed
Expand Down
8 changes: 6 additions & 2 deletions doc/modules/ROOT/pages/nrepl-api/ops.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,9 @@ Returns::

=== `content-type-middleware`

Enhances the ``eval`` op by adding ``content-type`` and friends to some responses. Not an op in itself.
Enhances the ``eval`` op by adding ``content-type`` and ``body`` to certain ``eval`` responses. Not an op in itself.

Depending on the type of the return value of the evaluation this middleware may kick in and include a representation of the result in the response, together with a MIME/Media type to indicate how it should be handled by the client. Comes with implementations for ``URI``, ``URL``, ``File``, and ``java.awt.Image``. More type handlers can be provided by the user by extending the ``cider.nrepl.middleware.content-type/content-type-response`` multimethod. This dispatches using ``clojure.core/type``, so ``:type`` metadata on plain Clojure values can be used to provide custom handling.

Required parameters::
{blank}
Expand All @@ -168,7 +170,9 @@ Optional parameters::


Returns::
{blank}
* `:body` The rich response document, if applicable.
* `:content-type` The Media type (MIME type) of the reponse, structured as a pair, `[type {:as attrs}]`
* `:content-transfer-encoding` The encoding of the response body (Optional, currently only one possible value `"base64"`)


=== `debug-input`
Expand Down
6 changes: 6 additions & 0 deletions doc/modules/ROOT/pages/nrepl-api/supplied_middleware.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@
| `complete`
| Code completion.

| `wrap-content-type`
| -
| No
| `eval`
| Rich content handling, return multimedia results beyond plain text from `eval`.

| `wrap-debug`
| -
| No
Expand Down
83 changes: 43 additions & 40 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
Expand Down Expand Up @@ -74,43 +75,45 @@
(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 [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 ^URL (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.)]
(merge response
(when (ImageIO/write ^RenderedImage value "png" ^OutputStream bos)
(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 ^URL (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 (when (ImageIO/write ^RenderedImage value "png" ^OutputStream bos)
(slurp-reply "" ["image/png" {}] (.toByteArray bos))))))

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

(send [_this response]
(.send transport (response+content-type response)))))
(send [this response]
(.send transport (content-type-response response)))))

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