Skip to content

Commit

Permalink
Add support for binary resource download (fixes #2108)
Browse files Browse the repository at this point in the history
* also improve clojuredoc strings
  • Loading branch information
allentiak committed Oct 21, 2024
1 parent 22c6269 commit 8b93e1d
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 6 deletions.
3 changes: 3 additions & 0 deletions modules/rest-api/src/blaze/rest_api/routes.clj
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
:compile (fn [{:keys [response-type] :response-type.json/keys [opts]} _]
(condp = response-type
:json (output/wrap-output opts)
:binary (fhir-output/wrap-binary-output opts)
:none identity
fhir-output/wrap-output))})

Expand Down Expand Up @@ -102,6 +103,8 @@
{:fhir.resource/type name}
[""
(cond-> {:name (keyword name "type")}
(= name "Binary")
(assoc :response-type :binary)
(contains? interactions :search-type)
(assoc :get {:interaction "search-type"
:middleware [[wrap-db node db-sync-timeout]
Expand Down
2 changes: 1 addition & 1 deletion modules/rest-api/src/blaze/rest_api/spec.clj
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@
boolean?)

(s/def ::operation/response-type
#{:json})
#{:json :binary})

(s/def ::operation/resource-types
(s/coll-of string?))
Expand Down
39 changes: 37 additions & 2 deletions modules/rest-util/src/blaze/middleware/fhir/output.clj
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
(ns blaze.middleware.fhir.output
"JSON/XML serialization middleware."
"FHIR Resource serialization middleware.
Currently supported formats:
* standard: JSON, XML;
* special: binary."
(:require
[blaze.anomaly :as ba]
[blaze.fhir.spec :as fhir-spec]
[blaze.fhir.spec.type :as fhir-type]
[blaze.handler.util :as handler-util]
[clojure.data.xml :as xml]
[clojure.java.io :as io]
[cognitect.anomalies :as anom]
[muuntaja.parse :as parse]
[prometheus.alpha :as prom]
[ring.util.response :as ring]
[taoensso.timbre :as log])
(:import
[java.io ByteArrayOutputStream]))
[java.io ByteArrayOutputStream]
[java.util Base64]))

(set! *warn-on-reflection* true)

Expand Down Expand Up @@ -45,6 +52,12 @@
(with-open [_ (prom/timer generate-duration-seconds "xml")]
(generate-xml* body)))

(defn- generate-binary [body]
(log/trace "generate binary")
(with-open [_ (prom/timer generate-duration-seconds "binary")]
(when (:data body)
(.decode (Base64/getDecoder) ^String (fhir-type/value (:data body))))))

(defn- encode-response-json [{:keys [body] :as response} content-type]
(cond-> response body (-> (update :body generate-json)
(ring/content-type content-type))))
Expand All @@ -53,6 +66,14 @@
(cond-> response body (-> (update :body generate-xml)
(ring/content-type content-type))))

(defn- encode-response-binary [{:keys [body] :as response}]
(let [content-type (or (-> response :body :contentType fhir-type/value)
"application/octet-stream")]
(cond->
(ring/content-type response content-type)
body (-> (update :body generate-binary)))))


(defn- format-key [format]
(condp = format
"application/fhir+json" :fhir+json
Expand Down Expand Up @@ -92,3 +113,17 @@
([handler opts]
(fn [request respond raise]
(handler request #(respond (handle-response opts request %)) raise))))

(defn handle-binary-response [opts request response]
(case (request-format request)
:fhir+json (encode-response-json response "application/fhir+json;charset=utf-8")
:fhir+xml (encode-response-xml response "application/fhir+xml;charset=utf-8")
(encode-response-binary response)))

(defn wrap-binary-output
"Middleware to output binary resources."
([handler]
(wrap-binary-output handler {}))
([handler opts]
(fn [request respond raise]
(handler request #(respond (handle-binary-response opts request %)) raise))))
5 changes: 4 additions & 1 deletion modules/rest-util/src/blaze/middleware/fhir/resource.clj
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
(ns blaze.middleware.fhir.resource
"JSON/XML deserialization middleware."
"FHIR Resource deserialization middleware.
Currently supported formats:
* standard: JSON, XML."
(:require
[blaze.anomaly :as ba :refer [if-ok when-ok]]
[blaze.async.comp :as ac]
Expand Down
5 changes: 4 additions & 1 deletion modules/rest-util/src/blaze/middleware/output.clj
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
(ns blaze.middleware.output
"JSON serialization middleware."
"Non-FHIR serialization middleware.
Currently supported formats:
* standard: JSON."
(:require
[jsonista.core :as j]
[ring.util.response :as ring]))
Expand Down
49 changes: 48 additions & 1 deletion modules/rest-util/test/blaze/middleware/fhir/output_test.clj
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
(ns blaze.middleware.fhir.output-test
(:require
[blaze.byte-string :as bs]
[blaze.fhir.spec :as fhir-spec]
[blaze.fhir.spec-spec]
[blaze.fhir.spec.type :as fhir-type]
[blaze.fhir.test-util]
[blaze.middleware.fhir.output :refer [wrap-output]]
[blaze.middleware.fhir.output :refer [wrap-binary-output wrap-output]]
[blaze.module.test-util.ring :refer [call]]
[blaze.test-util :as tu]
[clojure.data.xml :as xml]
Expand All @@ -27,6 +29,19 @@
(fn [_ respond _]
(respond (ring/response {:fhir/type :fhir/Patient :id "0"})))))

(defn binary-resource-handler-200
"A handler which uses the binary middleware and just returns
a binary resource."
[{:keys [content-type data] :as _body}]
(wrap-binary-output
(fn [_ respond _]
(respond
(ring/response
(cond->
{:fhir/type :fhir/Binary}
data (assoc :data (fhir-type/base64Binary data))
content-type (assoc :contentType (fhir-type/code content-type))))))))

(def resource-handler-304
"A handler which returns a 304 Not Modified response."
(wrap-output
Expand Down Expand Up @@ -205,5 +220,37 @@
[:headers "Content-Type"] := "application/fhir+xml;charset=utf-8"
[:body parse-xml :issue 0 :diagnostics] := "Invalid white space character (0x1e) in text to output (in xml 1.1, could output as a character entity)")))

(deftest binary-resource-test

(testing "with data and with content type"
(given (call (binary-resource-handler-200 {:content-type "text/plain" :data "MTA1NjE0Cg=="}) {:headers {"accept" "text/plain"}})
:status := 200
[:headers "Content-Type"] := "text/plain"
[:body bs/from-byte-array] := #blaze/byte-string"3130353631340A"))

(testing "with data and without content type"
(given (call (binary-resource-handler-200 {:content-type nil :data "MTA1NjE0Cg=="}) {:headers {"accept" "text/plain"}})
:status := 200
[:headers "Content-Type"] := "application/octet-stream"
[:body bs/from-byte-array] := #blaze/byte-string"3130353631340A"))

(testing "without data and with content type"
(given (call (binary-resource-handler-200 {:content-type "text/plain"}) {:headers {"accept" "text/plain"}})
:status := 200
[:headers "Content-Type"] := "text/plain"
:body := nil))

(testing "without data and without content type"
(given (call (binary-resource-handler-200 {:content-type nil}) {:headers {"accept" "text/plain"}})
:status := 200
[:headers "Content-Type"] := "application/octet-stream"
:body := nil))

(testing "without body at all"
(given (call (binary-resource-handler-200 nil) {:headers {"accept" "text/plain"}})
:status := 200
[:headers "Content-Type"] := "application/octet-stream"
:body := nil)))

(deftest not-acceptable-test
(is (nil? (call resource-handler-200 {:headers {"accept" "text/plain"}}))))

0 comments on commit 8b93e1d

Please sign in to comment.