2b9c35b599ad9d298111539c4dc144ee417749ea — Joost Diepenmaat 2 years ago 95722b8
middleware 1st attempt. update dependencies
M project.clj => project.clj +2 -7
@@ 4,11 4,6 @@
  :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0"
            :url "https://www.eclipse.org/legal/epl-2.0/"}
  :dependencies [[org.clojure/clojure "1.10.0" :scope "provided"]
                 [cheshire "5.10.0" :exclusions [com.fasterxml.jackson.core/jackson-core]]
                 [com.atlassian.oai/swagger-request-validator-core "2.18.1"]]
  :profiles {:dev {:dependencies [[org.clojure/data.json "2.4.0"]]
                   :resource-paths ["dev-resources"]
                   :plugins [[lein-codox "0.10.7"]]
                   :codox {:metadata {:doc/format :markdown}
                           :output-path "codox"}}}
  :deploy-repositories [["releases" {:url "https://repo.clojars.org" :creds :gpg}]])

  :profiles {:dev {:resource-paths ["dev-resources"]}})

M src/nl/zeekat/ring_openapi_validator.clj => src/nl/zeekat/ring_openapi_validator.clj +51 -1
@@ 1,5 1,6 @@
(ns nl.zeekat.ring-openapi-validator
  (:require [clojure.string :as string])
  (:require [clojure.string :as string]
            [cheshire.core :as json])
  (:import com.atlassian.oai.validator.OpenApiInteractionValidator

@@ 119,6 120,7 @@
   (openapi-validator spec {})))

(defn validate-interaction
  "Validate a `request`/`response` pair using the given `validator`.

@@ 142,3 144,51 @@
  If any issues are found, returns a report collection"
  [validator method path response]
  (report->coll (.validateResponse validator path (ring->Method method) (ring->Response response))))

;; https://stackoverflow.com/questions/5034311/multiple-readers-for-inputstream-in-java/30262036#30262036
;; strategy: copy the input steam /once/ so that there can be exactly
;; one downstream reader, and the memory can be freed after - fully
;; immutable means there's no way to tell when we're "done" with the
;; request body.

(defn- immutable-request-body
  "Replace mutable InputStream body with the equivalent re-readable content.

  Ensures that any multiple handlers/middleware can read the body
  content without stepping on each other's toes.

  Returns a new ring request"
  [{:keys [body] :as request}]
  (if (instance? java.io.InputStream body)
    (assoc request :body (slurp body))

(defn wrap-request-validator
  "Middleware validating requests against an OOAPI spec.

    - `f` is the handler to wrap
    - `validator` should be an `OpenApiInteractionValidator` - see

  Each incoming request is validated using [[validate-request]]. When
  errors are found, the a 400 Bad Request response is returned with the
  error collection as the response body. When the request is valid
  according to the validator, it is passed along to the original

      (-> my-api-handler
          (wrap-validator (openapi-validator \"path/to/spec.json\"))

  Since the error response body is just clojure map, you need some other
  middleware like `ring.middleware.json` to turn it into full ring
  [f validator]
  (fn [request]
    (let [request (immutable-request-body request)]
      (if-let [errs (validate-request validator request)]
        {:status 400 ; bad request
         :body   errs}
        (f request)))))

M test/nl/zeekat/ring_openapi_validator_test.clj => test/nl/zeekat/ring_openapi_validator_test.clj +3 -3
@@ 1,5 1,5 @@
(ns nl.zeekat.ring-openapi-validator-test
  (:require [clojure.data.json :as json]
  (:require [cheshire.core :as json]
            [clojure.java.io :as io]
            [clojure.test :refer [deftest testing is]]
            [nl.zeekat.ring-openapi-validator :as validator]))

@@ 30,11 30,11 @@

(def ooapi-response  {:headers {"Content-Type" ooapi-content-type}
                      :status  200
                      :body    (json/json-str ooapi-resource)})
                      :body    (json/generate-string ooapi-resource)})

(def ooapi-invalid-response  {:headers {"Content-Type" ooapi-content-type}
                              :status  200
                              :body    (json/json-str (dissoc ooapi-resource :owner))})
                              :body    (json/generate-string (dissoc ooapi-resource :owner))})

(deftest test-openapi-validator
  (let [validator (validator/openapi-validator "ooapi.json" {})]