diff --git a/modules/rest-api/src/blaze/rest_api/routes.clj b/modules/rest-api/src/blaze/rest_api/routes.clj index 115fee9b1..f57572bdb 100644 --- a/modules/rest-api/src/blaze/rest_api/routes.clj +++ b/modules/rest-api/src/blaze/rest_api/routes.clj @@ -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))}) @@ -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] diff --git a/modules/rest-api/src/blaze/rest_api/spec.clj b/modules/rest-api/src/blaze/rest_api/spec.clj index 4caddf582..c92cf7cd9 100644 --- a/modules/rest-api/src/blaze/rest_api/spec.clj +++ b/modules/rest-api/src/blaze/rest_api/spec.clj @@ -102,7 +102,7 @@ boolean?) (s/def ::operation/response-type - #{:json}) + #{:json :binary}) (s/def ::operation/resource-types (s/coll-of string?)) diff --git a/modules/rest-util/src/blaze/middleware/fhir/output.clj b/modules/rest-util/src/blaze/middleware/fhir/output.clj index 177af7469..593826e1d 100644 --- a/modules/rest-util/src/blaze/middleware/fhir/output.clj +++ b/modules/rest-util/src/blaze/middleware/fhir/output.clj @@ -1,8 +1,13 @@ (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] @@ -11,7 +16,8 @@ [ring.util.response :as ring] [taoensso.timbre :as log]) (:import - [java.io ByteArrayOutputStream])) + [java.io ByteArrayOutputStream] + [java.util Base64])) (set! *warn-on-reflection* true) @@ -45,6 +51,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)))) @@ -53,6 +65,12 @@ (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 @@ -92,3 +110,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)))) diff --git a/modules/rest-util/src/blaze/middleware/fhir/resource.clj b/modules/rest-util/src/blaze/middleware/fhir/resource.clj index 4b417b455..9e391d770 100644 --- a/modules/rest-util/src/blaze/middleware/fhir/resource.clj +++ b/modules/rest-util/src/blaze/middleware/fhir/resource.clj @@ -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] diff --git a/modules/rest-util/src/blaze/middleware/output.clj b/modules/rest-util/src/blaze/middleware/output.clj index 75268f186..3b479c5d8 100644 --- a/modules/rest-util/src/blaze/middleware/output.clj +++ b/modules/rest-util/src/blaze/middleware/output.clj @@ -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])) diff --git a/modules/rest-util/test/blaze/middleware/fhir/output_test.clj b/modules/rest-util/test/blaze/middleware/fhir/output_test.clj index 6322d57eb..a2b2312b9 100644 --- a/modules/rest-util/test/blaze/middleware/fhir/output_test.clj +++ b/modules/rest-util/test/blaze/middleware/fhir/output_test.clj @@ -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] @@ -27,6 +29,18 @@ (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 @@ -205,5 +219,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"}}))))