~jomco/ring-openapi-validator

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
           com.atlassian.oai.validator.model.Request
           com.atlassian.oai.validator.model.Request$Method


@@ 119,6 120,7 @@
  ([spec]
   (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))))


;; FIXME:
;; 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))
    request))

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

    - `f` is the handler to wrap
    - `validator` should be an `OpenApiInteractionValidator` - see
      [[openapi-validator]].

  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
  handler.

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

  Since the error response body is just clojure map, you need some other
  middleware like `ring.middleware.json` to turn it into full ring
  response."
  [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" {})]