From c576b476346b32ebe13347d3c096b53d75548909 Mon Sep 17 00:00:00 2001 From: Enzzo Cavallo Date: Tue, 30 Aug 2022 13:24:21 -0300 Subject: [PATCH 01/50] OpenAPI V3 Support --- modules/reitit-core/src/reitit/coercion.cljc | 2 + .../src/reitit/coercion/malli.cljc | 74 ++- modules/reitit-openapi/project.clj | 12 + .../reitit-openapi/src/reitit/openapi.cljc | 109 ++++ .../src/reitit/coercion/schema.cljc | 28 + .../reitit-spec/src/reitit/coercion/spec.cljc | 27 + project.clj | 3 +- test/cljc/reitit/openapi_test.clj | 552 ++++++++++++++++++ 8 files changed, 805 insertions(+), 2 deletions(-) create mode 100644 modules/reitit-openapi/project.clj create mode 100644 modules/reitit-openapi/src/reitit/openapi.cljc create mode 100644 test/cljc/reitit/openapi_test.clj diff --git a/modules/reitit-core/src/reitit/coercion.cljc b/modules/reitit-core/src/reitit/coercion.cljc index 0fd5d234..a8b0d9c6 100644 --- a/modules/reitit-core/src/reitit/coercion.cljc +++ b/modules/reitit-core/src/reitit/coercion.cljc @@ -146,6 +146,7 @@ :path :path :multipart :formData}] (case specification + :openapi (-get-apidocs coercion specification data) :swagger (->> (update data :parameters @@ -156,6 +157,7 @@ (into {})))) (-get-apidocs coercion specification))))) + ;; ;; integration ;; diff --git a/modules/reitit-malli/src/reitit/coercion/malli.cljc b/modules/reitit-malli/src/reitit/coercion/malli.cljc index a57d2641..949da00f 100644 --- a/modules/reitit-malli/src/reitit/coercion/malli.cljc +++ b/modules/reitit-malli/src/reitit/coercion/malli.cljc @@ -5,6 +5,7 @@ [malli.edn :as edn] [malli.error :as me] [malli.experimental.lite :as l] + [malli.json-schema :as json-schema] [malli.swagger :as swagger] [malli.transform :as mt] [malli.util :as mu] @@ -132,6 +133,76 @@ ;; malli options :options nil}) +(defn -get-apidocs-openapi + [coercion {:keys [parameters responses content-types] :or {content-types ["application/json"]}} options] + (let [{:keys [body request]} parameters + parameters (dissoc parameters :request :body) + ->schema-object (fn [schema opts] + (let [current-opts (merge options opts)] + (json-schema/transform (coercion/-compile-model coercion schema current-opts) + current-opts)))] + + (merge + (when (seq parameters) + {:parameters + (->> (for [[in schema] parameters + :let [{:keys [properties required] :as root} (->schema-object schema {:in in :type :parameter}) + required? (partial contains? (set required))] + [k schema] properties] + (merge {:in (name in) + :name k + :required (required? k) + :schema schema} + (select-keys root [:description]))) + (into []))}) + (when body + ;; body uses a single schema to describe every :requestBody + ;; the schema-object transformer should be able to transform into distinct content-types + {:requestBody {:content (into {} + (map (fn [content-type] + (let [schema (->schema-object body {:in :requestBody + :type :schema + :content-type content-type})] + [content-type {:schema schema}]))) + content-types)}}) + (when request + ;; request allow to different :requestBody per content-type + {:requestBody + {:content + (into {} + (map (fn [[content-type requestBody]] + (let [schema (->schema-object requestBody {:in :requestBody + :type :schema + :content-type content-type})] + [content-type {:schema schema}]))) + (:content request))}}) + (when responses + {:responses + (into {} + (map (fn [[status {:keys [body content] + :as response}]] + (let [content (merge + (when body + (into {} + (map (fn [content-type] + (let [schema (->schema-object body {:in :responses + :type :schema + :content-type content-type})] + [content-type {:schema schema}]))) + content-types)) + (when content + (into {} + (map (fn [[content-type schema]] + (let [schema (->schema-object schema {:in :responses + :type :schema + :content-type content-type})] + [content-type {:schema schema}]))) + content)))] + [status (merge (select-keys response [:description]) + (when content + {:content content}))]))) + responses)})))) + (defn create ([] (create nil)) @@ -145,7 +216,7 @@ (reify coercion/Coercion (-get-name [_] :malli) (-get-options [_] opts) - (-get-apidocs [_ specification {:keys [parameters responses]}] + (-get-apidocs [this specification {:keys [parameters responses] :as data}] (case specification :swagger (merge (if parameters @@ -167,6 +238,7 @@ (update :schema compile options) (update :schema swagger/transform {:type :schema})) $))]))})) + :openapi (-get-apidocs-openapi this data options) (throw (ex-info (str "Can't produce Schema apidocs for " specification) diff --git a/modules/reitit-openapi/project.clj b/modules/reitit-openapi/project.clj new file mode 100644 index 00000000..535b51ec --- /dev/null +++ b/modules/reitit-openapi/project.clj @@ -0,0 +1,12 @@ +(defproject metosin/reitit-openapi "0.5.18" + :description "Reitit: OpenAPI-support" + :url "https://github.com/metosin/reitit" + :license {:name "Eclipse Public License" + :url "http://www.eclipse.org/legal/epl-v10.html"} + :scm {:name "git" + :url "https://github.com/metosin/reitit" + :dir "../.."} + :plugins [[lein-parent "0.3.8"]] + :parent-project {:path "../../project.clj" + :inherit [:deploy-repositories :managed-dependencies]} + :dependencies [[metosin/reitit-core]]) diff --git a/modules/reitit-openapi/src/reitit/openapi.cljc b/modules/reitit-openapi/src/reitit/openapi.cljc new file mode 100644 index 00000000..6b1a465e --- /dev/null +++ b/modules/reitit-openapi/src/reitit/openapi.cljc @@ -0,0 +1,109 @@ +(ns reitit.openapi + (:require [clojure.set :as set] + [clojure.spec.alpha :as s] + [clojure.string :as str] + [meta-merge.core :refer [meta-merge]] + [reitit.coercion :as coercion] + [reitit.core :as r] + [reitit.trie :as trie])) + +(s/def ::id (s/or :keyword keyword? :set (s/coll-of keyword? :into #{}))) +(s/def ::no-doc boolean?) +(s/def ::tags (s/coll-of (s/or :keyword keyword? :string string?) :kind #{})) +(s/def ::summary string?) +(s/def ::description string?) + +(s/def ::openapi (s/keys :opt-un [::id])) +(s/def ::spec (s/keys :opt-un [::openapi ::no-doc ::tags ::summary ::description])) + +(def openapi-feature + "Feature for handling openapi-documentation for routes. + Works both with Middleware & Interceptors. Does not participate + in actual request processing, just provides specs for the new + documentation keys for the route data. Should be accompanied by a + [[openapi-spec-handler]] to expose the openapi spec. + + New route data keys contributing to openapi docs: + + | key | description | + | --------------|-------------| + | :openapi | map of any openapi-data. Must have `:id` (keyword or sequence of keywords) to identify the api + | :no-doc | optional boolean to exclude endpoint from api docs + | :summary | optional short string summary of an endpoint + | :description | optional long description of an endpoint. Supports http://spec.commonmark.org/ + + Also the coercion keys contribute to openapi spec: + + | key | description | + | --------------|-------------| + | :parameters | optional input parameters for a route, in a format defined by the coercion + | :responses | optional descriptions of responses, in a format defined by coercion + + Example: + + [\"/api\" + {:openapi {:id :my-api} + :middleware [reitit.openapi/openapi-feature]} + + [\"/openapi.json\" + {:get {:no-doc true + :openapi {:info {:title \"my-api\"}} + :handler reitit.openapi/openapi-spec-handler}}] + + [\"/plus\" + {:get {:openapi {:tags \"math\"} + :summary \"adds numbers together\" + :description \"takes `x` and `y` query-params and adds them together\" + :parameters {:query {:x int?, :y int?}} + :responses {200 {:body {:total pos-int?}}} + :handler (fn [{:keys [parameters]}] + {:status 200 + :body (+ (-> parameters :query :x) + (-> parameters :query :y)})}}]]" + {:name ::openapi + :spec ::spec}) + +(defn- openapi-path [path opts] + (-> path (trie/normalize opts) (str/replace #"\{\*" "{"))) + +(defn create-openapi-handler + "Create a ring handler to emit openapi spec. Collects all routes from router which have + an intersecting `[:openapi :id]` and which are not marked with `:no-doc` route data." + [] + (fn create-openapi + ([{::r/keys [router match] :keys [request-method]}] + (let [{:keys [id] :or {id ::default} :as openapi} (-> match :result request-method :data :openapi) + ids (trie/into-set id) + strip-top-level-keys #(dissoc % :id :info :host :basePath :definitions :securityDefinitions) + strip-endpoint-keys #(dissoc % :id :parameters :responses :summary :description) + openapi (->> (strip-endpoint-keys openapi) + (merge {:openapi "3.1.0" + :x-id ids})) + accept-route (fn [route] + (-> route second :openapi :id (or ::default) (trie/into-set) (set/intersection ids) seq)) + ;base-openapi-spec {:responses ^:displace {:default {:description ""}}} + transform-endpoint (fn [[method {{:keys [coercion no-doc openapi] :as data} :data + middleware :middleware + interceptors :interceptors}]] + (if (and data (not no-doc)) + [method + (meta-merge + #_base-openapi-spec + (apply meta-merge (keep (comp :openapi :data) middleware)) + (apply meta-merge (keep (comp :openapi :data) interceptors)) + (if coercion + (coercion/get-apidocs coercion :openapi data)) + (select-keys data [:tags :summary :description]) + (strip-top-level-keys openapi))])) + transform-path (fn [[p _ c]] + (if-let [endpoint (some->> c (keep transform-endpoint) (seq) (into {}))] + [(openapi-path p (r/options router)) endpoint])) + map-in-order #(->> % (apply concat) (apply array-map)) + paths (->> router (r/compiled-routes) (filter accept-route) (map transform-path) map-in-order)] + {:status 200 + :body (meta-merge openapi {:paths paths})})) + ([req res raise] + (try + (res (create-openapi req)) + (catch #?(:clj Exception :cljs :default) e + (raise e)))))) diff --git a/modules/reitit-schema/src/reitit/coercion/schema.cljc b/modules/reitit-schema/src/reitit/coercion/schema.cljc index 022f3872..b647b846 100644 --- a/modules/reitit-schema/src/reitit/coercion/schema.cljc +++ b/modules/reitit-schema/src/reitit/coercion/schema.cljc @@ -4,6 +4,7 @@ [reitit.coercion :as coercion] [schema-tools.coerce :as stc] [schema-tools.core :as st] + [schema-tools.openapi.core :as openapi] [schema-tools.swagger.core :as swagger] [schema.coerce :as sc] [schema.core :as s] @@ -67,6 +68,33 @@ (if (:schema $) (update $ :schema #(coercion/-compile-model this % nil)) $))]))}))) + :openapi (merge + (when (seq (dissoc parameters :body :request)) + (openapi/openapi-spec {::openapi/parameters + (into + (empty parameters) + (for [[k v] (dissoc parameters :body :request)] + [k (coercion/-compile-model this v nil)]))})) + (when (:body parameters) + {:requestBody (openapi/openapi-spec + {::openapi/content {"application/json" (:body parameters)}})}) + (when (:request parameters) + {:requestBody (openapi/openapi-spec + {::openapi/content (:content (:request parameters))})}) + (when responses + {:responses + (into + (empty responses) + (for [[k response] responses] + [k (merge + (select-keys response [:description]) + (when (:body response) + (openapi/openapi-spec + {::openapi/content {"application/json" (coercion/-compile-model this (:body response) nil)}})) + (when (:content response) + (openapi/openapi-spec + {::openapi/content (:content response)})))]))})) + (throw (ex-info (str "Can't produce Schema apidocs for " specification) diff --git a/modules/reitit-spec/src/reitit/coercion/spec.cljc b/modules/reitit-spec/src/reitit/coercion/spec.cljc index 5f9809a7..0184a2e0 100644 --- a/modules/reitit-spec/src/reitit/coercion/spec.cljc +++ b/modules/reitit-spec/src/reitit/coercion/spec.cljc @@ -4,6 +4,7 @@ [reitit.coercion :as coercion] [spec-tools.core :as st #?@(:cljs [:refer [Spec]])] [spec-tools.data-spec :as ds #?@(:cljs [:refer [Maybe]])] + [spec-tools.openapi.core :as openapi] [spec-tools.swagger.core :as swagger]) #?(:clj (:import (spec_tools.core Spec) @@ -105,6 +106,32 @@ (if (:schema $) (update $ :schema #(coercion/-compile-model this % nil)) $))]))}))) + :openapi (openapi/openapi-spec + (merge + (when (seq (dissoc parameters :body :request)) + {::openapi/parameters + (into (empty parameters) + (for [[k v] (dissoc parameters :body :request)] + [k (coercion/-compile-model this v nil)]))}) + (when (:body parameters) + {:requestBody (openapi/openapi-spec + {::openapi/content {"application/json" (coercion/-compile-model this (:body parameters) nil)}})}) + (when (:request parameters) + {:requestBody (openapi/openapi-spec + {::openapi/content (coercion/-compile-model this (:content (:request parameters)) nil)})}) + (when responses + {:responses + (into + (empty responses) + (for [[k response] responses] + [k (merge + (select-keys response [:description]) + (when (:body response) + (openapi/openapi-spec + {::openapi/content {"application/json" (coercion/-compile-model this (:body response) nil)}})) + (when (:content response) + (openapi/openapi-spec + {::openapi/content (coercion/-compile-model this (:content response) nil)})))]))}))) (throw (ex-info (str "Can't produce Spec apidocs for " specification) diff --git a/project.clj b/project.clj index 8afdf260..893860b4 100644 --- a/project.clj +++ b/project.clj @@ -65,6 +65,7 @@ "modules/reitit-ring/src" "modules/reitit-http/src" "modules/reitit-middleware/src" + "modules/reitit-openapi/src" "modules/reitit-interceptors/src" "modules/reitit-malli/src" "modules/reitit-spec/src" @@ -86,7 +87,7 @@ [metosin/muuntaja "0.6.8"] [metosin/sieppari "0.0.0-alpha13"] [metosin/jsonista "0.3.5"] - [metosin/malli "0.8.2"] + [metosin/malli "0.8.9"] [lambdaisland/deep-diff "0.0-47"] [meta-merge "1.0.0"] [com.bhauman/spell-spec "0.1.2"] diff --git a/test/cljc/reitit/openapi_test.clj b/test/cljc/reitit/openapi_test.clj new file mode 100644 index 00000000..22d43453 --- /dev/null +++ b/test/cljc/reitit/openapi_test.clj @@ -0,0 +1,552 @@ +(ns reitit.openapi-test + (:require [clojure.test :refer [deftest is testing]] + [muuntaja.core :as m] + [reitit.coercion.malli :as malli] + [reitit.coercion.schema :as schema] + [reitit.coercion.spec :as spec] + [reitit.openapi :as openapi] + [reitit.ring :as ring] + [reitit.ring.coercion :as rrc] + [reitit.swagger-ui :as swagger-ui] + [schema.core :as s] + [spec-tools.data-spec :as ds])) + +(def app + (ring/ring-handler + (ring/router + ["/api" + {:openapi {:id ::math}} + + ["/openapi.json" + {:get {:no-doc true + :openapi {:info {:title "my-api"}} + :handler (openapi/create-openapi-handler)}}] + + #_["/spec" {:coercion spec/coercion} + ["/plus/:z" + {:patch {:summary "patch" + :handler (constantly {:status 200})} + :options {:summary "options" + :middleware [{:data {:openapi {:responses {200 {:description "200"}}}}}] + :handler (constantly {:status 200})} + :get {:summary "plus" + :parameters {:query {:x int?, :y int?} + :path {:z int?}} + :openapi {:responses {400 {:description "kosh" + :content {"application/json" {:schema {:type "string"}}}}}} + :responses {200 {:body {:total int?}} + 500 {:description "fail"}} + :handler (fn [{{{:keys [x y]} :query + {:keys [z]} :path} :parameters}] + {:status 200, :body {:total (+ x y z)}})} + :post {:summary "plus with body" + :parameters {:body (ds/maybe [int?]) + :path {:z int?}} + :openapi {:responses {400 {:content {"application/json" {:schema {:type "string"}}} + :description "kosh"}}} + :responses {200 {:body {:total int?}} + 500 {:description "fail"}} + :handler (fn [{{{:keys [z]} :path + xs :body} :parameters}] + {:status 200, :body {:total (+ (reduce + xs) z)}})}}]] + + ["/malli" {:coercion malli/coercion} + ["/plus/*z" + {:get {:summary "plus" + :parameters {:query [:map [:x int?] [:y int?]] + :path [:map [:z int?]]} + :openapi {:responses {400 {:description "kosh" + :content {"application/json" {:schema {:type "string"}}}}}} + :responses {200 {:body [:map [:total int?]]} + 500 {:description "fail"}} + :handler (fn [{{{:keys [x y]} :query + {:keys [z]} :path} :parameters}] + {:status 200, :body {:total (+ x y z)}})} + :post {:summary "plus with body" + :parameters {:body [:maybe [:vector int?]] + :path [:map [:z int?]]} + :openapi {:responses {400 {:description "kosh" + :content {"application/json" {:schema {:type "string"}}}}}} + :responses {200 {:body [:map [:total int?]]} + 500 {:description "fail"}} + :handler (fn [{{{:keys [z]} :path + xs :body} :parameters}] + {:status 200, :body {:total (+ (reduce + xs) z)}})}}]] + + ["/schema" {:coercion schema/coercion} + ["/plus/*z" + {:get {:summary "plus" + :parameters {:query {:x s/Int, :y s/Int} + :path {:z s/Int}} + :openapi {:responses {400 {:content {"application/json" {:schema {:type "string"}}} + :description "kosh"}}} + :responses {200 {:body {:total s/Int}} + 500 {:description "fail"}} + :handler (fn [{{{:keys [x y]} :query + {:keys [z]} :path} :parameters}] + {:status 200, :body {:total (+ x y z)}})} + :post {:summary "plus with body" + :parameters {:body (s/maybe [s/Int]) + :path {:z s/Int}} + :openapi {:responses {400 {:content {"application/json" {:schema {:type "string"}}} + :description "kosh"}}} + :responses {200 {:body {:total s/Int}} + 500 {:description "fail"}} + :handler (fn [{{{:keys [z]} :path + xs :body} :parameters}] + {:status 200, :body {:total (+ (reduce + xs) z)}})}}]]] + + {:data {:middleware [openapi/openapi-feature + rrc/coerce-exceptions-middleware + rrc/coerce-request-middleware + rrc/coerce-response-middleware]}}))) + +(require '[fipp.edn]) +(deftest openapi-test + (testing "endpoints work" + (testing "malli" + (is (= {:body {:total 6}, :status 200} + (app {:request-method :get + :uri "/api/malli/plus/3" + :query-params {:x "2", :y "1"}}))) + (is (= {:body {:total 7}, :status 200} + (app {:request-method :post + :uri "/api/malli/plus/3" + :body-params [1 3]}))))) + (testing "openapi-spec" + (let [spec (:body (app {:request-method :get + :uri "/api/openapi.json"})) + expected {:x-id #{::math} + :openapi "3.1.0" + :info {:title "my-api"} + :paths {#_#_"/api/spec/plus/{z}" {:patch {:summary "patch" + :responses {:default {:description ""}}} + :options {:summary "options" + :responses {200 {:description "200"}}} + :get {:parameters [{:in "query" + :name "x" + :description "" + :required true + :schema {:type "integer"}} + {:in "query" + :name "y" + :description "" + :required true + :schema {:type "integer"}} + {:in "path" + :name "z" + :description "" + :required true + :schema {:type "integer"}}] + :responses {200 {:content {"application/json" {:schema {:type "object" + :properties {"total" {:format "int64" + :type "integer"}} + :required ["total"]}}}} + 400 {:description "kosh" + :content {"application/json" {:schema {:type "string"}}}} + 500 {:description "fail"}} + :summary "plus"} + :post {:parameters [{:in "path" + :name "z" + :required true + :schema {:type "integer"}}] + :requestBody {:content {"application/json" {:schema {:oneOf [{:items {:type "integer"} + :type "array"} + {:type "null"}]}}}} + :responses {200 {:content {"application/json" {:schema {:properties {"total" {:format "int64" + :type "integer"}} + :required ["total"] + :type "object"}}}} + 400 {:content {"application/json" {:schema {:type "string"}}} + :description "kosh"} + 500 {:description "fail"}} + :summary "plus with body"}} + "/api/malli/plus/{z}" {:get {:parameters [{:in "query" + :name :x + :required true + :schema {:type "integer"}} + {:in "query" + :name :y + :required true + :schema {:type "integer"}} + {:in "path" + :name :z + :required true + :schema {:type "integer"}}] + :responses {200 {:content {"application/json" {:schema {:type "object" + :properties {:total {:type "integer"}} + :required [:total]}}}} + 400 {:description "kosh" + :content {"application/json" {:schema {:type "string"}}}} + 500 {:description "fail"}} + :summary "plus"} + :post {:parameters [{:in "path" + :name :z + :schema {:type "integer"} + :required true}] + :requestBody {:content {"application/json" {:schema {:oneOf [{:items {:type "integer"} + :type "array"} + {:type "null"}]}}}} + :responses {200 {:content {"application/json" {:schema {:properties {:total {:type "integer"}} + :required [:total] + :type "object"}}}} + 400 {:description "kosh" + :content {"application/json" {:schema {:type "string"}}}} + 500 {:description "fail"}} + :summary "plus with body"}} + "/api/schema/plus/{z}" {:get {:parameters [{:description "" + :in "query" + :name "x" + :required true + :schema {:format "int32" + :type "integer"}} + {:description "" + :in "query" + :name "y" + :required true + :schema {:type "integer" + :format "int32"}} + {:in "path" + :name "z" + :description "" + :required true + :schema {:type "integer" + :format "int32"}}] + :responses {200 {:content {"application/json" {:schema {:additionalProperties false + :properties {"total" {:format "int32" + :type "integer"}} + :required ["total"] + :type "object"}}}} + 400 {:description "kosh" + :content {"application/json" {:schema {:type "string"}}}} + 500 {:description "fail"}} + :summary "plus"} + :post {:parameters [{:in "path" + :name "z" + :description "" + :required true + :schema {:type "integer" + :format "int32"}}] + :requestBody {:content {"application/json" {:schema {:oneOf [{:type "array" + :items {:type "integer" + :format "int32"}} + {:type "null"}]}}}} + :responses {200 {:content {"application/json" {:schema {:properties {"total" {:format "int32" + :type "integer"}} + :additionalProperties false + :required ["total"] + :type "object"}}}} + 400 {:description "kosh" + :content {"application/json" {:schema {:type "string"}}}} + 500 {:description "fail"}} + :summary "plus with body"}}}}] + (is (= expected spec))))) + +(defn spec-paths [app uri] + (-> {:request-method :get, :uri uri} app :body :paths keys)) + +(deftest multiple-openapi-apis-test + (let [ping-route ["/ping" {:get (constantly "ping")}] + spec-route ["/openapi.json" + {:get {:no-doc true + :handler (openapi/create-openapi-handler)}}] + app (ring/ring-handler + (ring/router + [["/common" {:openapi {:id #{::one ::two}}} + ping-route] + + ["/one" {:openapi {:id ::one}} + ping-route + spec-route] + + ["/two" {:openapi {:id ::two}} + ping-route + spec-route + ["/deep" {:openapi {:id ::one}} + ping-route]] + ["/one-two" {:openapi {:id #{::one ::two}}} + spec-route]]))] + (is (= ["/common/ping" "/one/ping" "/two/deep/ping"] + (spec-paths app "/one/openapi.json"))) + (is (= ["/common/ping" "/two/ping"] + (spec-paths app "/two/openapi.json"))) + (is (= ["/common/ping" "/one/ping" "/two/ping" "/two/deep/ping"] + (spec-paths app "/one-two/openapi.json"))))) + +(deftest openapi-ui-config-test + (let [app (swagger-ui/create-swagger-ui-handler + {:path "/" + :url "/openapi.json" + :config {:jsonEditor true}})] + (is (= 302 (:status (app {:request-method :get, :uri "/"})))) + (is (= 200 (:status (app {:request-method :get, :uri "/index.html"})))) + (is (= {:jsonEditor true, :url "/openapi.json"} + (->> {:request-method :get, :uri "/config.json"} + (app) :body (m/decode m/instance "application/json")))))) + +(deftest without-openapi-id-test + (let [app (ring/ring-handler + (ring/router + [["/ping" + {:get (constantly "ping")}] + ["/openapi.json" + {:get {:no-doc true + :handler (openapi/create-openapi-handler)}}]]))] + (is (= ["/ping"] (spec-paths app "/openapi.json"))) + (is (= #{::openapi/default} + (-> {:request-method :get :uri "/openapi.json"} + (app) :body :x-id))))) + +(deftest with-options-endpoint-test + (let [app (ring/ring-handler + (ring/router + [["/ping" + {:options (constantly "options")}] + ["/pong" + (constantly "options")] + ["/openapi.json" + {:get {:no-doc true + :handler (openapi/create-openapi-handler)}}]]))] + (is (= ["/ping" "/pong"] (spec-paths app "/openapi.json"))) + (is (= #{::openapi/default} + (-> {:request-method :get :uri "/openapi.json"} + (app) :body :x-id))))) + +(deftest malli-all-parameter-types-test + (let [app (ring/ring-handler + (ring/router + [["/parameters" + {:post {:coercion malli/coercion + :parameters {:query [:map + [:q :string]] + :body [:map + [:b :string]] + :header [:map + [:h :string]] + :cookie [:map + [:c :string]] + :path [:map + [:p :string]]} + :responses {200 {:body [:map [:ok :string]]}} + :handler identity}}] + ["/openapi.json" + {:get {:handler (openapi/create-openapi-handler) + :no-doc true}}]])) + spec (-> {:request-method :get + :uri "/openapi.json"} + app + :body)] + (testing + "all non-body parameters" + (is (= [{:in "query" + :name :q + :required true + :schema {:type "string"}} + {:in "header" + :name :h + :required true + :schema {:type "string"}} + {:in "cookie" + :name :c + :required true + :schema {:type "string"}} + {:in "path" + :name :p + :required true + :schema {:type "string"}}] + (-> spec + (get-in [:paths "/parameters" :post :parameters]) + #_(doto clojure.pprint/pprint))))) + (testing + "body parameter" + (is (= {"application/json" {:schema {:type "object" + :properties {:b {:type "string"}} + :required [:b]}}} + (-> spec + (get-in [:paths "/parameters" :post :requestBody :content]) + #_(doto clojure.pprint/pprint))))) + (testing + "body response" + (is (= {"application/json" {:schema {:type "object" + :properties {:ok {:type "string"}} + :required [:ok]}}} + (-> spec + (get-in [:paths "/parameters" :post :responses 200 :content]) + #_(doto clojure.pprint/pprint))))))) + +(deftest malli-all-parameter-types-test-per-content-type + (let [app (ring/ring-handler + (ring/router + [["/parameters" + {:post {:coercion malli/coercion + :parameters {:query [:map + [:q :string]] + :request {:content {"application/json" [:map + [:b :string]]}} + :header [:map + [:h :string]] + :cookie [:map + [:c :string]] + :path [:map + [:p :string]]} + :responses {200 {:content {"application/json" [:map [:ok :string]]}}} + :handler identity}}] + ["/openapi.json" + {:get {:handler (openapi/create-openapi-handler) + :no-doc true}}]])) + spec (-> {:request-method :get + :uri "/openapi.json"} + app + :body)] + (testing + "all non-body parameters" + (is (= [{:in "query" + :name :q + :required true + :schema {:type "string"}} + {:in "header" + :name :h + :required true + :schema {:type "string"}} + {:in "cookie" + :name :c + :required true + :schema {:type "string"}} + {:in "path" + :name :p + :required true + :schema {:type "string"}}] + (-> spec + (get-in [:paths "/parameters" :post :parameters]) + #_(doto clojure.pprint/pprint))))) + (testing + "body parameter" + (is (= {"application/json" {:schema {:type "object" + :properties {:b {:type "string"}} + :required [:b]}}} + (-> spec + (get-in [:paths "/parameters" :post :requestBody :content]) + #_(doto clojure.pprint/pprint))))) + (testing + "body response" + (is (= {"application/json" {:schema {:type "object" + :properties {:ok {:type "string"}} + :required [:ok]}}} + (-> spec + (get-in [:paths "/parameters" :post :responses 200 :content]) + #_(doto clojure.pprint/pprint))))))) + + +(deftest schema-all-parameter-types-test-per-content-type + (let [app (ring/ring-handler + (ring/router + [["/parameters" + {:post {:coercion schema/coercion + :parameters {:query {:q s/Str} + :request {:content {"application/json" {:b s/Str}}} + :header {:h s/Str} + :cookie {:c s/Str} + :path {:p s/Str}} + :responses {200 {:content {"application/json" {:ok s/Str}}}} + :handler identity}}] + ["/openapi.json" + {:get {:handler (openapi/create-openapi-handler) + :no-doc true}}]])) + spec (-> {:request-method :get + :uri "/openapi.json"} + app + :body)] + (testing + "all non-body parameters" + (is (= [{:description "" + :in "query" + :name "q" + :required true + :schema {:type "string"}} + {:description "" + :in "header" + :name "h" + :required true + :schema {:type "string"}} + {:description "" + :in "cookie" + :name "c" + :required true + :schema {:type "string"}} + {:description "" + :in "path" + :name "p" + :required true + :schema {:type "string"}}] + (-> spec + (get-in [:paths "/parameters" :post :parameters]) + #_(doto clojure.pprint/pprint))))) + (testing + "body parameter" + (is (= {"application/json" {:schema {:additionalProperties false + :properties {"b" {:type "string"}} + :required ["b"] + :type "object"}}} + (-> spec + (get-in [:paths "/parameters" :post :requestBody :content]) + #_(doto clojure.pprint/pprint))))) + (testing + "body response" + (is (= {"application/json" {:schema {:additionalProperties false + :properties {"ok" {:type "string"}} + :required ["ok"] + :type "object"}}} + (-> spec + (get-in [:paths "/parameters" :post :responses 200 :content]) + #_(doto clojure.pprint/pprint))))))) +(deftest all-parameter-types-test + (let [app (ring/ring-handler + (ring/router + [["/parameters" + {:post {:coercion spec/coercion + :parameters {:query {:q string?} + :body {:b string?} + :cookies {:c string?} + :header {:h string?} + :path {:p string?}} + :responses {200 {:body {:ok string?}}} + :handler identity}}] + ["/openapi.json" + {:get {:no-doc true + :handler (openapi/create-openapi-handler)}}]])) + spec (:body (app {:request-method :get, :uri "/openapi.json"}))] + (is (= [{:description "" + :in "query" + :name "q" + :required true + :schema {:type "string"}} + {:description "" + :in "cookies" + :name "c" + :required true + :schema {:type "string"}} + {:description "" + :in "header" + :name "h" + :required true + :schema {:type "string"}} + {:description "" + :in "path" + :name "p" + :required true + :schema {:type "string"}}] + (-> spec + (get-in [:paths "/parameters" :post :parameters]) + #_(doto clojure.pprint/pprint)))) + (is (= {"application/json" {:schema {:properties {"b" {:type "string"}} + :required ["b"] + :type "object"}}} + (-> spec + (get-in [:paths "/parameters" :post :requestBody :content]) + #_(doto clojure.pprint/pprint)))) + (is (= {"application/json" {:schema {:properties {"ok" {:type "string"}} + :required ["ok"] + :type "object"}}} + (-> spec + (get-in [:paths "/parameters" :post :responses 200 :content]) + #_(doto clojure.pprint/pprint)))))) From f03134e215935e7b660904a3e5e1ccb5c0b0fd41 Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Wed, 15 Feb 2023 16:07:23 +0200 Subject: [PATCH 02/50] fix: malli openapi tests malli.json-schema now outputs `:additionalProperties false` --- test/cljc/reitit/openapi_test.clj | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/cljc/reitit/openapi_test.clj b/test/cljc/reitit/openapi_test.clj index 22d43453..9971c13c 100644 --- a/test/cljc/reitit/openapi_test.clj +++ b/test/cljc/reitit/openapi_test.clj @@ -175,6 +175,7 @@ :schema {:type "integer"}}] :responses {200 {:content {"application/json" {:schema {:type "object" :properties {:total {:type "integer"}} + :additionalProperties false :required [:total]}}}} 400 {:description "kosh" :content {"application/json" {:schema {:type "string"}}}} @@ -189,6 +190,7 @@ {:type "null"}]}}}} :responses {200 {:content {"application/json" {:schema {:properties {:total {:type "integer"}} :required [:total] + :additionalProperties false :type "object"}}}} 400 {:description "kosh" :content {"application/json" {:schema {:type "string"}}}} @@ -361,6 +363,7 @@ "body parameter" (is (= {"application/json" {:schema {:type "object" :properties {:b {:type "string"}} + :additionalProperties false :required [:b]}}} (-> spec (get-in [:paths "/parameters" :post :requestBody :content]) @@ -369,6 +372,7 @@ "body response" (is (= {"application/json" {:schema {:type "object" :properties {:ok {:type "string"}} + :additionalProperties false :required [:ok]}}} (-> spec (get-in [:paths "/parameters" :post :responses 200 :content]) @@ -423,6 +427,7 @@ "body parameter" (is (= {"application/json" {:schema {:type "object" :properties {:b {:type "string"}} + :additionalProperties false :required [:b]}}} (-> spec (get-in [:paths "/parameters" :post :requestBody :content]) @@ -431,6 +436,7 @@ "body response" (is (= {"application/json" {:schema {:type "object" :properties {:ok {:type "string"}} + :additionalProperties false :required [:ok]}}} (-> spec (get-in [:paths "/parameters" :post :responses 200 :content]) From 8f48cdc96cb7b4cf704b14b9f46dafe5e5f9039e Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Wed, 15 Feb 2023 16:23:59 +0200 Subject: [PATCH 03/50] test: enable openapi spec tests --- test/cljc/reitit/openapi_test.clj | 95 +++++++++++++++---------------- 1 file changed, 46 insertions(+), 49 deletions(-) diff --git a/test/cljc/reitit/openapi_test.clj b/test/cljc/reitit/openapi_test.clj index 9971c13c..3c3e4bc8 100644 --- a/test/cljc/reitit/openapi_test.clj +++ b/test/cljc/reitit/openapi_test.clj @@ -22,14 +22,9 @@ :openapi {:info {:title "my-api"}} :handler (openapi/create-openapi-handler)}}] - #_["/spec" {:coercion spec/coercion} + ["/spec" {:coercion spec/coercion} ["/plus/:z" - {:patch {:summary "patch" - :handler (constantly {:status 200})} - :options {:summary "options" - :middleware [{:data {:openapi {:responses {200 {:description "200"}}}}}] - :handler (constantly {:status 200})} - :get {:summary "plus" + {:get {:summary "plus" :parameters {:query {:x int?, :y int?} :path {:z int?}} :openapi {:responses {400 {:description "kosh" @@ -119,48 +114,50 @@ expected {:x-id #{::math} :openapi "3.1.0" :info {:title "my-api"} - :paths {#_#_"/api/spec/plus/{z}" {:patch {:summary "patch" - :responses {:default {:description ""}}} - :options {:summary "options" - :responses {200 {:description "200"}}} - :get {:parameters [{:in "query" - :name "x" - :description "" - :required true - :schema {:type "integer"}} - {:in "query" - :name "y" - :description "" - :required true - :schema {:type "integer"}} - {:in "path" - :name "z" - :description "" - :required true - :schema {:type "integer"}}] - :responses {200 {:content {"application/json" {:schema {:type "object" - :properties {"total" {:format "int64" - :type "integer"}} - :required ["total"]}}}} - 400 {:description "kosh" - :content {"application/json" {:schema {:type "string"}}}} - 500 {:description "fail"}} - :summary "plus"} - :post {:parameters [{:in "path" - :name "z" - :required true - :schema {:type "integer"}}] - :requestBody {:content {"application/json" {:schema {:oneOf [{:items {:type "integer"} - :type "array"} - {:type "null"}]}}}} - :responses {200 {:content {"application/json" {:schema {:properties {"total" {:format "int64" - :type "integer"}} - :required ["total"] - :type "object"}}}} - 400 {:content {"application/json" {:schema {:type "string"}}} - :description "kosh"} - 500 {:description "fail"}} - :summary "plus with body"}} + :paths {"/api/spec/plus/{z}" {:get {:parameters [{:in "query" + :name "x" + :description "" + :required true + :schema {:type "integer" + :format "int64"}} + {:in "query" + :name "y" + :description "" + :required true + :schema {:type "integer" + :format "int64"}} + {:in "path" + :name "z" + :description "" + :required true + :schema {:type "integer" + :format "int64"}}] + :responses {200 {:content {"application/json" {:schema {:type "object" + :properties {"total" {:format "int64" + :type "integer"}} + :required ["total"]}}}} + 400 {:description "kosh" + :content {"application/json" {:schema {:type "string"}}}} + 500 {:description "fail"}} + :summary "plus"} + :post {:parameters [{:in "path" + :name "z" + :required true + :description "" + :schema {:type "integer" + :format "int64"}}] + :requestBody {:content {"application/json" {:schema {:oneOf [{:items {:type "integer" + :format "int64"} + :type "array"} + {:type "null"}]}}}} + :responses {200 {:content {"application/json" {:schema {:properties {"total" {:format "int64" + :type "integer"}} + :required ["total"] + :type "object"}}}} + 400 {:content {"application/json" {:schema {:type "string"}}} + :description "kosh"} + 500 {:description "fail"}} + :summary "plus with body"}} "/api/malli/plus/{z}" {:get {:parameters [{:in "query" :name :x :required true From c8d679c6b3d41ad160754188731ae4505c3f6b97 Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Thu, 2 Mar 2023 14:16:56 +0200 Subject: [PATCH 04/50] feat: per-content-type request/response coercions implemented on the reitit-core level so individual coercions don't need changes syntax: {:parameters {:request {:content {"application/edn" [:map ...]}}} :responses {200 {:content {"application/edn" [:map ...]}}}} --- modules/reitit-core/src/reitit/coercion.cljc | 47 +++++++++---- test/cljc/reitit/ring_coercion_test.cljc | 73 ++++++++++++++++++++ 2 files changed, 106 insertions(+), 14 deletions(-) diff --git a/modules/reitit-core/src/reitit/coercion.cljc b/modules/reitit-core/src/reitit/coercion.cljc index 442409da..6e55b209 100644 --- a/modules/reitit-core/src/reitit/coercion.cljc +++ b/modules/reitit-core/src/reitit/coercion.cljc @@ -37,6 +37,7 @@ (def ^:no-doc default-parameter-coercion {:query (->ParameterCoercion :query-params :string true true) :body (->ParameterCoercion :body-params :body false false) + :request (->ParameterCoercion :body-params :request false false) :form (->ParameterCoercion :form-params :string true true) :header (->ParameterCoercion :headers :string true true) :path (->ParameterCoercion :path-params :string true true)}) @@ -84,11 +85,22 @@ (if coercion (if-let [{:keys [keywordize? open? in style]} (parameter-coercion type)] (let [transform (comp (if keywordize? walk/keywordize-keys identity) in) - model (if open? (-open-model coercion model) model)] - (if-let [coercer (-request-coercer coercion style model)] + ->open (if open? #(-open-model coercion %) identity) + format-coercer-pairs (if (= :request style) + (for [[format schema] (:content model)] + [format (-request-coercer coercion :body (->open schema))]) + [[:default (-request-coercer coercion style (->open model))]]) + format->coercer (some->> format-coercer-pairs + (filter second) + (seq) + (into {}))] + (when format->coercer (fn [request] (let [value (transform request) format (extract-request-format request) + coercer (or (format->coercer format) + (format->coercer :default) + (fn [value _format] value)) result (coercer value format)] (if (error? result) (request-coercion-failed! result coercion value in request serialize-failed-result) @@ -97,17 +109,24 @@ (defn extract-response-format-default [request _] (-> request :muuntaja/response :format)) -(defn response-coercer [coercion body {:keys [extract-response-format serialize-failed-result] - :or {extract-response-format extract-response-format-default}}] +(defn response-coercer [coercion {:keys [content body]} {:keys [extract-response-format serialize-failed-result] + :or {extract-response-format extract-response-format-default}}] (if coercion - (if-let [coercer (-response-coercer coercion body)] - (fn [request response] - (let [format (extract-response-format request response) - value (:body response) - result (coercer value format)] - (if (error? result) - (response-coercion-failed! result coercion value request response serialize-failed-result) - result)))))) + (let [per-format-coercers (some->> (for [[format schema] content] + [format (-response-coercer coercion schema)]) + (filter second) + (seq) + (into {})) + default (when body (-response-coercer coercion body))] + (when (or per-format-coercers default) + (fn [request response] + (let [format (extract-response-format request response) + value (:body response) + coercer (get per-format-coercers format (or default (fn [value _format] value))) + result (coercer value format)] + (if (error? result) + (response-coercion-failed! result coercion value request response serialize-failed-result) + result))))))) (defn encode-error [data] (-> data @@ -136,8 +155,8 @@ (into {}))) (defn response-coercers [coercion responses opts] - (some->> (for [[status {:keys [body]}] responses :when body] - [status (response-coercer coercion body opts)]) + (some->> (for [[status model] responses] + [status (response-coercer coercion model opts)]) (filter second) (seq) (into {}))) diff --git a/test/cljc/reitit/ring_coercion_test.cljc b/test/cljc/reitit/ring_coercion_test.cljc index e8bee456..c8287c9f 100644 --- a/test/cljc/reitit/ring_coercion_test.cljc +++ b/test/cljc/reitit/ring_coercion_test.cljc @@ -13,6 +13,7 @@ [reitit.ring :as ring] [reitit.ring.coercion :as rrc] [schema.core :as s] + [clojure.spec.alpha] [spec-tools.data-spec :as ds]) #?(:clj (:import (clojure.lang ExceptionInfo) @@ -582,6 +583,78 @@ (is (= {:status 200, :body {:total "FOO: this, BAR: that"}} (call m/schema custom-meta-merge-checking-schema))) (is (= {:status 200, :body {:total "FOO: this, BAR: that"}} (call identity custom-meta-merge-checking-parameters))))))) +(deftest per-content-type-test + (doseq [[coercion json-request edn-request default-request json-response edn-response default-response] + [[#'malli/coercion + [:map [:request [:enum :json]] [:response any?]] + [:map [:request [:enum :edn]] [:response any?]] + [:map [:request [:enum :default]] [:response any?]] + [:map [:request any?] [:response [:enum :json]]] + [:map [:request any?] [:response [:enum :edn]]] + [:map [:request any?] [:response [:enum :default]]]] + [#'schema/coercion + {:request (s/eq :json) :response s/Any} + {:request (s/eq :edn) :response s/Any} + {:request (s/eq :default) :response s/Any} + {:request s/Any :response (s/eq :json)} + {:request s/Any :response (s/eq :edn)} + {:request s/Any :response (s/eq :default)}] + [#'spec/coercion + {:request (clojure.spec.alpha/spec #{:json}) :response any?} + {:request (clojure.spec.alpha/spec #{:edn}) :response any?} + {:request (clojure.spec.alpha/spec #{:default}) :response any?} + {:request any? :response (clojure.spec.alpha/spec #{:json})} + {:request any? :response (clojure.spec.alpha/spec #{:end})} + {:request any? :response (clojure.spec.alpha/spec #{:default})}]]] + (testing coercion + (let [app (ring/ring-handler + (ring/router + [["/foo" {:post {:parameters {:request {:content {"application/json" json-request + "application/edn" edn-request + :default default-request}}} + :responses {200 {:content {"application/json" json-response + "application/edn" edn-response} + :body default-response}} + :handler (fn [req] + {:status 200 + :body (-> req :parameters :request)})}}]] + {:data {:middleware [rrc/coerce-request-middleware + rrc/coerce-response-middleware] + :coercion @coercion}})) + call (fn [request] + (try + (app request) + (catch ExceptionInfo e + (select-keys (ex-data e) [:type :in])))) + request (fn [request-format response-format body] + {:request-method :post + :uri "/foo" + :muuntaja/request {:format request-format} + :muuntaja/response {:format response-format} + :body-params body})] + (testing "succesful call" + (is (= {:status 200 :body {:request :json, :response :json}} + (call (request "application/json" "application/json" {:request :json :response :json})))) + (is (= {:status 200 :body {:request :edn, :response :json}} + (call (request "application/edn" "application/json" {:request :edn :response :json})))) + (is (= {:status 200 :body {:request :default, :response :default}} + (call (request "application/transit" "application/transit" {:request :default :response :default}))))) + (testing "request validation fails" + (is (= {:type :reitit.coercion/request-coercion :in [:request :body-params]} + (call (request "application/edn" "application/json" {:request :json :response :json})))) + (is (= {:type :reitit.coercion/request-coercion :in [:request :body-params]} + (call (request "application/json" "application/json" {:request :edn :response :json})))) + (is (= {:type :reitit.coercion/request-coercion :in [:request :body-params]} + (call (request "application/transit" "application/json" {:request :edn :response :json}))))) + (testing "response validation fails" + (is (= {:type :reitit.coercion/response-coercion :in [:response :body]} + (call (request "application/json" "application/json" {:request :json :response :edn})))) + (is (= {:type :reitit.coercion/response-coercion :in [:response :body]} + (call (request "application/json" "application/edn" {:request :json :response :json})))) + (is (= {:type :reitit.coercion/response-coercion :in [:response :body]} + (call (request "application/json" "application/transit" {:request :json :response :json}))))))))) + + #?(:clj (deftest muuntaja-test (let [app (ring/ring-handler From b149c8c5af615b9dbe7c40db04ce774257acf21c Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Thu, 16 Feb 2023 10:26:54 +0200 Subject: [PATCH 05/50] test: rework openapi3 tests - DRY out all-parameter-types-test and per-content-type-test - Remove redundant assertions - Run same test for malli/schema/spec - Clean up commented-out code etc. --- project.clj | 1 + test/cljc/reitit/openapi_test.clj | 371 +++++++++++------------------- 2 files changed, 131 insertions(+), 241 deletions(-) diff --git a/project.clj b/project.clj index 50501565..d4376587 100644 --- a/project.clj +++ b/project.clj @@ -105,6 +105,7 @@ [org.clojure/test.check "1.1.1"] [org.clojure/tools.namespace "1.3.0"] [com.gfredericks/test.chuck "0.2.13"] + [nubank/matcher-combinators "3.8.3"] [io.pedestal/pedestal.service "0.5.10"] diff --git a/test/cljc/reitit/openapi_test.clj b/test/cljc/reitit/openapi_test.clj index 3c3e4bc8..4eebc98d 100644 --- a/test/cljc/reitit/openapi_test.clj +++ b/test/cljc/reitit/openapi_test.clj @@ -1,5 +1,7 @@ (ns reitit.openapi-test (:require [clojure.test :refer [deftest is testing]] + [jsonista.core :as j] + [matcher-combinators.test :refer [match?]] [muuntaja.core :as m] [reitit.coercion.malli :as malli] [reitit.coercion.schema :as schema] @@ -96,7 +98,6 @@ rrc/coerce-request-middleware rrc/coerce-response-middleware]}}))) -(require '[fipp.edn]) (deftest openapi-test (testing "endpoints work" (testing "malli" @@ -311,245 +312,133 @@ (-> {:request-method :get :uri "/openapi.json"} (app) :body :x-id))))) -(deftest malli-all-parameter-types-test - (let [app (ring/ring-handler - (ring/router - [["/parameters" - {:post {:coercion malli/coercion - :parameters {:query [:map - [:q :string]] - :body [:map - [:b :string]] - :header [:map - [:h :string]] - :cookie [:map - [:c :string]] - :path [:map - [:p :string]]} - :responses {200 {:body [:map [:ok :string]]}} - :handler identity}}] - ["/openapi.json" - {:get {:handler (openapi/create-openapi-handler) - :no-doc true}}]])) - spec (-> {:request-method :get - :uri "/openapi.json"} - app - :body)] - (testing - "all non-body parameters" - (is (= [{:in "query" - :name :q - :required true - :schema {:type "string"}} - {:in "header" - :name :h - :required true - :schema {:type "string"}} - {:in "cookie" - :name :c - :required true - :schema {:type "string"}} - {:in "path" - :name :p - :required true - :schema {:type "string"}}] - (-> spec - (get-in [:paths "/parameters" :post :parameters]) - #_(doto clojure.pprint/pprint))))) - (testing - "body parameter" - (is (= {"application/json" {:schema {:type "object" - :properties {:b {:type "string"}} - :additionalProperties false - :required [:b]}}} - (-> spec - (get-in [:paths "/parameters" :post :requestBody :content]) - #_(doto clojure.pprint/pprint))))) - (testing - "body response" - (is (= {"application/json" {:schema {:type "object" - :properties {:ok {:type "string"}} - :additionalProperties false - :required [:ok]}}} - (-> spec - (get-in [:paths "/parameters" :post :responses 200 :content]) - #_(doto clojure.pprint/pprint))))))) +(defn- normalize + "Normalize format of openapi spec by converting it to json and back. + Handles differences like :q vs \"q\" in openapi generation." + [data] + (-> data + j/write-value-as-string + (j/read-value j/keyword-keys-object-mapper))) -(deftest malli-all-parameter-types-test-per-content-type - (let [app (ring/ring-handler - (ring/router - [["/parameters" - {:post {:coercion malli/coercion - :parameters {:query [:map - [:q :string]] - :request {:content {"application/json" [:map - [:b :string]]}} - :header [:map - [:h :string]] - :cookie [:map - [:c :string]] - :path [:map - [:p :string]]} - :responses {200 {:content {"application/json" [:map [:ok :string]]}}} - :handler identity}}] - ["/openapi.json" - {:get {:handler (openapi/create-openapi-handler) - :no-doc true}}]])) - spec (-> {:request-method :get - :uri "/openapi.json"} - app - :body)] - (testing - "all non-body parameters" - (is (= [{:in "query" - :name :q - :required true - :schema {:type "string"}} - {:in "header" - :name :h - :required true - :schema {:type "string"}} - {:in "cookie" - :name :c - :required true - :schema {:type "string"}} - {:in "path" - :name :p - :required true - :schema {:type "string"}}] - (-> spec - (get-in [:paths "/parameters" :post :parameters]) - #_(doto clojure.pprint/pprint))))) - (testing - "body parameter" - (is (= {"application/json" {:schema {:type "object" - :properties {:b {:type "string"}} - :additionalProperties false - :required [:b]}}} - (-> spec - (get-in [:paths "/parameters" :post :requestBody :content]) - #_(doto clojure.pprint/pprint))))) - (testing - "body response" - (is (= {"application/json" {:schema {:type "object" - :properties {:ok {:type "string"}} - :additionalProperties false - :required [:ok]}}} - (-> spec - (get-in [:paths "/parameters" :post :responses 200 :content]) - #_(doto clojure.pprint/pprint))))))) - - -(deftest schema-all-parameter-types-test-per-content-type - (let [app (ring/ring-handler - (ring/router - [["/parameters" - {:post {:coercion schema/coercion - :parameters {:query {:q s/Str} - :request {:content {"application/json" {:b s/Str}}} - :header {:h s/Str} - :cookie {:c s/Str} - :path {:p s/Str}} - :responses {200 {:content {"application/json" {:ok s/Str}}}} - :handler identity}}] - ["/openapi.json" - {:get {:handler (openapi/create-openapi-handler) - :no-doc true}}]])) - spec (-> {:request-method :get - :uri "/openapi.json"} - app - :body)] - (testing - "all non-body parameters" - (is (= [{:description "" - :in "query" - :name "q" - :required true - :schema {:type "string"}} - {:description "" - :in "header" - :name "h" - :required true - :schema {:type "string"}} - {:description "" - :in "cookie" - :name "c" - :required true - :schema {:type "string"}} - {:description "" - :in "path" - :name "p" - :required true - :schema {:type "string"}}] - (-> spec - (get-in [:paths "/parameters" :post :parameters]) - #_(doto clojure.pprint/pprint))))) - (testing - "body parameter" - (is (= {"application/json" {:schema {:additionalProperties false - :properties {"b" {:type "string"}} - :required ["b"] - :type "object"}}} - (-> spec - (get-in [:paths "/parameters" :post :requestBody :content]) - #_(doto clojure.pprint/pprint))))) - (testing - "body response" - (is (= {"application/json" {:schema {:additionalProperties false - :properties {"ok" {:type "string"}} - :required ["ok"] - :type "object"}}} - (-> spec - (get-in [:paths "/parameters" :post :responses 200 :content]) - #_(doto clojure.pprint/pprint))))))) (deftest all-parameter-types-test - (let [app (ring/ring-handler - (ring/router - [["/parameters" - {:post {:coercion spec/coercion - :parameters {:query {:q string?} - :body {:b string?} - :cookies {:c string?} - :header {:h string?} - :path {:p string?}} - :responses {200 {:body {:ok string?}}} - :handler identity}}] - ["/openapi.json" - {:get {:no-doc true - :handler (openapi/create-openapi-handler)}}]])) - spec (:body (app {:request-method :get, :uri "/openapi.json"}))] - (is (= [{:description "" - :in "query" - :name "q" - :required true - :schema {:type "string"}} - {:description "" - :in "cookies" - :name "c" - :required true - :schema {:type "string"}} - {:description "" - :in "header" - :name "h" - :required true - :schema {:type "string"}} - {:description "" - :in "path" - :name "p" - :required true - :schema {:type "string"}}] - (-> spec - (get-in [:paths "/parameters" :post :parameters]) - #_(doto clojure.pprint/pprint)))) - (is (= {"application/json" {:schema {:properties {"b" {:type "string"}} - :required ["b"] - :type "object"}}} - (-> spec - (get-in [:paths "/parameters" :post :requestBody :content]) - #_(doto clojure.pprint/pprint)))) - (is (= {"application/json" {:schema {:properties {"ok" {:type "string"}} - :required ["ok"] - :type "object"}}} - (-> spec - (get-in [:paths "/parameters" :post :responses 200 :content]) - #_(doto clojure.pprint/pprint)))))) + (doseq [[coercion ->schema] + [[#'malli/coercion (fn [nom] [:map [nom :string]])] + [#'schema/coercion (fn [nom] {nom s/Str})] + [#'spec/coercion (fn [nom] {nom string?})]]] + (testing coercion + (let [app (ring/ring-handler + (ring/router + [["/parameters" + {:post {:coercion @coercion + :parameters {:query (->schema :q) + :body (->schema :b) + :header (->schema :h) + :cookie (->schema :c) + :path (->schema :p)} + :responses {200 {:body (->schema :ok)}} + :handler identity}}] + ["/openapi.json" + {:get {:handler (openapi/create-openapi-handler) + :no-doc true}}]])) + spec (-> {:request-method :get + :uri "/openapi.json"} + app + :body)] + (testing "all non-body parameters" + (is (match? [{:in "query" + :name "q" + :required true + :schema {:type "string"}} + {:in "header" + :name "h" + :required true + :schema {:type "string"}} + {:in "cookie" + :name "c" + :required true + :schema {:type "string"}} + {:in "path" + :name "p" + :required true + :schema {:type "string"}}] + (-> spec + (get-in [:paths "/parameters" :post :parameters]) + normalize)))) + (testing "body parameter" + (is (match? {:schema {:type "object" + :properties {:b {:type "string"}} + #_#_:additionalProperties false ;; not present for spec + :required ["b"]}} + (-> spec + (get-in [:paths "/parameters" :post :requestBody :content "application/json"]) + normalize)))) + (testing "body response" + (is (match? {:schema {:type "object" + :properties {:ok {:type "string"}} + #_#_:additionalProperties false ;; not present for spec + :required ["ok"]}} + (-> spec + (get-in [:paths "/parameters" :post :responses 200 :content "application/json"]) + normalize)))))))) + +(deftest per-content-type-test + (doseq [[coercion ->schema] + [[#'malli/coercion (fn [nom] [:map [nom :string]])] + [#'schema/coercion (fn [nom] {nom s/Str})] + #_ ;; Doesn't work yet + [#'spec/coercion (fn [nom] {nom string?})]]] + (testing coercion + (let [app (ring/ring-handler + (ring/router + [["/parameters" + {:post {:coercion @coercion + :parameters {:request {:content {"application/json" (->schema :b)}}} + :responses {200 {:content {"application/json" (->schema :ok)}}} + :handler (fn [req] + {:status 200 + :body (-> req :parameters :request)})}}] + ["/openapi.json" + {:get {:handler (openapi/create-openapi-handler) + :no-doc true}}]] + {:data {:middleware [rrc/coerce-request-middleware + rrc/coerce-response-middleware]}})) + spec (-> {:request-method :get + :uri "/openapi.json"} + app + :body)] + (testing "body parameter" + (is (= {:schema {:type "object" + :properties {:b {:type "string"}} + :additionalProperties false + :required ["b"]}} + (-> spec + (get-in [:paths "/parameters" :post :requestBody :content "application/json"]) + normalize)))) + (testing "body response" + (is (= {:schema {:type "object" + :properties {:ok {:type "string"}} + :additionalProperties false + :required ["ok"]}} + (-> spec + (get-in [:paths "/parameters" :post :responses 200 :content "application/json"]) + normalize)))) + (testing "validation" + (let [valid-query {:request-method :post + :uri "/parameters" + :muuntaja/request {:format "application/json"} + :muuntaja/response {:format "application/json"} + :body-params {:b "x"}}] + (testing "of output" + (is (= {:type :reitit.coercion/response-coercion + :in [:response :body]} + (try + (app (assoc valid-query :body-params {:b "x"})) + (catch clojure.lang.ExceptionInfo e + (select-keys (ex-data e) [:type :in])))))) + (testing "of input" + (is (= {:type :reitit.coercion/request-coercion + :in [:request :body-params]} + (try + (app (assoc valid-query :body-params {:z 1})) + (catch clojure.lang.ExceptionInfo e + (select-keys (ex-data e) [:type :in])))))))))))) From 2d607027694dd0c70035050ad8e9ad6467edcc90 Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Fri, 3 Mar 2023 11:47:01 +0200 Subject: [PATCH 06/50] fix: per-content-type openapi w/ spec --- .../reitit-spec/src/reitit/coercion/spec.cljc | 8 ++- test/cljc/reitit/openapi_test.clj | 63 ++++++++++++------- 2 files changed, 45 insertions(+), 26 deletions(-) diff --git a/modules/reitit-spec/src/reitit/coercion/spec.cljc b/modules/reitit-spec/src/reitit/coercion/spec.cljc index 0184a2e0..26647fb3 100644 --- a/modules/reitit-spec/src/reitit/coercion/spec.cljc +++ b/modules/reitit-spec/src/reitit/coercion/spec.cljc @@ -118,7 +118,9 @@ {::openapi/content {"application/json" (coercion/-compile-model this (:body parameters) nil)}})}) (when (:request parameters) {:requestBody (openapi/openapi-spec - {::openapi/content (coercion/-compile-model this (:content (:request parameters)) nil)})}) + {::openapi/content (into {} + (for [[format model] (:content (:request parameters))] + [format (coercion/-compile-model this model nil)]))})}) (when responses {:responses (into @@ -131,7 +133,9 @@ {::openapi/content {"application/json" (coercion/-compile-model this (:body response) nil)}})) (when (:content response) (openapi/openapi-spec - {::openapi/content (coercion/-compile-model this (:content response) nil)})))]))}))) + {::openapi/content (into {} + (for [[format model] (:content response)] + [format (coercion/-compile-model this model nil)]))})))]))}))) (throw (ex-info (str "Can't produce Spec apidocs for " specification) diff --git a/test/cljc/reitit/openapi_test.clj b/test/cljc/reitit/openapi_test.clj index 4eebc98d..dab5f798 100644 --- a/test/cljc/reitit/openapi_test.clj +++ b/test/cljc/reitit/openapi_test.clj @@ -385,15 +385,16 @@ (doseq [[coercion ->schema] [[#'malli/coercion (fn [nom] [:map [nom :string]])] [#'schema/coercion (fn [nom] {nom s/Str})] - #_ ;; Doesn't work yet [#'spec/coercion (fn [nom] {nom string?})]]] (testing coercion (let [app (ring/ring-handler (ring/router [["/parameters" {:post {:coercion @coercion - :parameters {:request {:content {"application/json" (->schema :b)}}} - :responses {200 {:content {"application/json" (->schema :ok)}}} + :parameters {:request {:content {"application/json" (->schema :b) + "application/edn" (->schema :c)}}} + :responses {200 {:content {"application/json" (->schema :ok) + "application/edn" (->schema :edn)}}} :handler (fn [req] {:status 200 :body (-> req :parameters :request)})}}] @@ -407,38 +408,52 @@ app :body)] (testing "body parameter" - (is (= {:schema {:type "object" - :properties {:b {:type "string"}} - :additionalProperties false - :required ["b"]}} - (-> spec - (get-in [:paths "/parameters" :post :requestBody :content "application/json"]) - normalize)))) + (is (match? {:schema {:type "object" + :properties {:b {:type "string"}} + #_#_:additionalProperties false ;; not present for spec + :required ["b"]}} + (-> spec + (get-in [:paths "/parameters" :post :requestBody :content "application/json"]) + normalize))) + (is (match? {:schema {:type "object" + :properties {:c {:type "string"}} + #_#_:additionalProperties false ;; not present for spec + :required ["c"]}} + (-> spec + (get-in [:paths "/parameters" :post :requestBody :content "application/edn"]) + normalize)))) (testing "body response" - (is (= {:schema {:type "object" - :properties {:ok {:type "string"}} - :additionalProperties false - :required ["ok"]}} - (-> spec - (get-in [:paths "/parameters" :post :responses 200 :content "application/json"]) - normalize)))) + (is (match? {:schema {:type "object" + :properties {:ok {:type "string"}} + #_#_:additionalProperties false ;; not present for spec + :required ["ok"]}} + (-> spec + (get-in [:paths "/parameters" :post :responses 200 :content "application/json"]) + normalize))) + (is (match? {:schema {:type "object" + :properties {:edn {:type "string"}} + #_#_:additionalProperties false ;; not present for spec + :required ["edn"]}} + (-> spec + (get-in [:paths "/parameters" :post :responses 200 :content "application/edn"]) + normalize)))) (testing "validation" - (let [valid-query {:request-method :post - :uri "/parameters" - :muuntaja/request {:format "application/json"} - :muuntaja/response {:format "application/json"} - :body-params {:b "x"}}] + (let [query {:request-method :post + :uri "/parameters" + :muuntaja/request {:format "application/json"} + :muuntaja/response {:format "application/json"} + :body-params {:b "x"}}] (testing "of output" (is (= {:type :reitit.coercion/response-coercion :in [:response :body]} (try - (app (assoc valid-query :body-params {:b "x"})) + (app query) (catch clojure.lang.ExceptionInfo e (select-keys (ex-data e) [:type :in])))))) (testing "of input" (is (= {:type :reitit.coercion/request-coercion :in [:request :body-params]} (try - (app (assoc valid-query :body-params {:z 1})) + (app (assoc query :body-params {:z 1})) (catch clojure.lang.ExceptionInfo e (select-keys (ex-data e) [:type :in])))))))))))) From c3a3ca9f95184af355d7563dac1faf99d8c52d8c Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Mon, 6 Mar 2023 10:36:52 +0200 Subject: [PATCH 07/50] feat: change syntax for :request parameter coercion default schema use :request :body instead of :request :content :default for symmetry with :response --- modules/reitit-core/src/reitit/coercion.cljc | 11 ++++++----- test/cljc/reitit/ring_coercion_test.cljc | 4 ++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/modules/reitit-core/src/reitit/coercion.cljc b/modules/reitit-core/src/reitit/coercion.cljc index b91a1237..f6075ae9 100644 --- a/modules/reitit-core/src/reitit/coercion.cljc +++ b/modules/reitit-core/src/reitit/coercion.cljc @@ -87,11 +87,12 @@ (if-let [{:keys [keywordize? open? in style]} (parameter-coercion type)] (let [transform (comp (if keywordize? walk/keywordize-keys identity) in) ->open (if open? #(-open-model coercion %) identity) - format-coercer-pairs (if (= :request style) - (for [[format schema] (:content model)] - [format (-request-coercer coercion :body (->open schema))]) - [[:default (-request-coercer coercion style (->open model))]]) - format->coercer (some->> format-coercer-pairs + format-schema-pairs (if (= :request style) + (conj (:content model) [:default (:body model)]) + [[:default model]]) + format->coercer (some->> (for [[format schema] format-schema-pairs + :when schema] + [format (-request-coercer coercion (case style :request :body style) (->open schema))]) (filter second) (seq) (into {}))] diff --git a/test/cljc/reitit/ring_coercion_test.cljc b/test/cljc/reitit/ring_coercion_test.cljc index c8287c9f..da6c804a 100644 --- a/test/cljc/reitit/ring_coercion_test.cljc +++ b/test/cljc/reitit/ring_coercion_test.cljc @@ -610,8 +610,8 @@ (let [app (ring/ring-handler (ring/router [["/foo" {:post {:parameters {:request {:content {"application/json" json-request - "application/edn" edn-request - :default default-request}}} + "application/edn" edn-request} + :body default-request}} :responses {200 {:content {"application/json" json-response "application/edn" edn-response} :body default-response}} From 4c990fb44f4022b9e1a6b038192ee9e17e7da5b4 Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Mon, 6 Mar 2023 09:12:56 +0200 Subject: [PATCH 08/50] feat: openapi default request/response schemas use a default schema from :request/:response :body for all specified :content-types --- .../src/reitit/coercion/malli.cljc | 26 ++++++++---- .../src/reitit/coercion/schema.cljc | 29 +++++++------ .../reitit-spec/src/reitit/coercion/spec.cljc | 33 ++++++++------- test/cljc/reitit/openapi_test.clj | 41 +++++++++++++++++++ 4 files changed, 94 insertions(+), 35 deletions(-) diff --git a/modules/reitit-malli/src/reitit/coercion/malli.cljc b/modules/reitit-malli/src/reitit/coercion/malli.cljc index 5d4a863a..9b66f8fa 100644 --- a/modules/reitit-malli/src/reitit/coercion/malli.cljc +++ b/modules/reitit-malli/src/reitit/coercion/malli.cljc @@ -141,7 +141,6 @@ (let [current-opts (merge options opts)] (json-schema/transform (coercion/-compile-model coercion schema current-opts) current-opts)))] - (merge (when (seq parameters) {:parameters @@ -165,17 +164,26 @@ :content-type content-type})] [content-type {:schema schema}]))) content-types)}}) + (when request ;; request allow to different :requestBody per content-type {:requestBody - {:content - (into {} - (map (fn [[content-type requestBody]] - (let [schema (->schema-object requestBody {:in :requestBody - :type :schema - :content-type content-type})] - [content-type {:schema schema}]))) - (:content request))}}) + {:content (merge + (when (:body request) + (into {} + (map (fn [content-type] + (let [schema (->schema-object (:body request) {:in :requestBody + :type :schema + :content-type content-type})] + [content-type {:schema schema}]))) + content-types)) + (into {} + (map (fn [[content-type requestBody]] + (let [schema (->schema-object requestBody {:in :requestBody + :type :schema + :content-type content-type})] + [content-type {:schema schema}]))) + (:content request)))}}) (when responses {:responses (into {} diff --git a/modules/reitit-schema/src/reitit/coercion/schema.cljc b/modules/reitit-schema/src/reitit/coercion/schema.cljc index c9f22a0f..07dc5ce6 100644 --- a/modules/reitit-schema/src/reitit/coercion/schema.cljc +++ b/modules/reitit-schema/src/reitit/coercion/schema.cljc @@ -47,8 +47,9 @@ (reify coercion/Coercion (-get-name [_] :schema) (-get-options [_] opts) - (-get-apidocs [this specification {:keys [parameters responses]}] - ;; TODO: this looks identical to spec, refactor when schema is done. + (-get-apidocs [this specification {:keys [parameters responses content-types] + :or {content-types ["application/json"]}}] + ;; TODO: this looks identical to spec, refactor when schema is done. (case specification :swagger (swagger/swagger-spec (merge @@ -76,24 +77,28 @@ (for [[k v] (dissoc parameters :body :request)] [k (coercion/-compile-model this v nil)]))})) (when (:body parameters) - {:requestBody (openapi/openapi-spec - {::openapi/content {"application/json" (:body parameters)}})}) + {:requestBody (openapi/openapi-spec + {::openapi/content (zipmap content-types (repeat (:body parameters)))})}) (when (:request parameters) - {:requestBody (openapi/openapi-spec - {::openapi/content (:content (:request parameters))})}) + {:requestBody (openapi/openapi-spec + {::openapi/content (merge + (when-let [default (get-in parameters [:request :body])] + (zipmap content-types (repeat default))) + (:content (:request parameters)))})}) (when responses {:responses (into (empty responses) - (for [[k response] responses] + (for [[k {:keys [body content] :as response}] responses] [k (merge (select-keys response [:description]) - (when (:body response) + (when (or body content) (openapi/openapi-spec - {::openapi/content {"application/json" (coercion/-compile-model this (:body response) nil)}})) - (when (:content response) - (openapi/openapi-spec - {::openapi/content (:content response)})))]))})) + {::openapi/content (merge + (when body + (zipmap content-types (repeat (coercion/-compile-model this body nil)))) + (when response + (:content response)))})))]))})) (throw (ex-info diff --git a/modules/reitit-spec/src/reitit/coercion/spec.cljc b/modules/reitit-spec/src/reitit/coercion/spec.cljc index 26647fb3..31050a01 100644 --- a/modules/reitit-spec/src/reitit/coercion/spec.cljc +++ b/modules/reitit-spec/src/reitit/coercion/spec.cljc @@ -86,7 +86,8 @@ (reify coercion/Coercion (-get-name [_] :spec) (-get-options [_] opts) - (-get-apidocs [this specification {:keys [parameters responses]}] + (-get-apidocs [this specification {:keys [parameters responses content-types] + :or {content-types ["application/json"]}}] (case specification :swagger (swagger/swagger-spec (merge @@ -114,28 +115,32 @@ (for [[k v] (dissoc parameters :body :request)] [k (coercion/-compile-model this v nil)]))}) (when (:body parameters) - {:requestBody (openapi/openapi-spec - {::openapi/content {"application/json" (coercion/-compile-model this (:body parameters) nil)}})}) + {:requestBody (openapi/openapi-spec + {::openapi/content (zipmap content-types (repeat (coercion/-compile-model this (:body parameters) nil)))})}) (when (:request parameters) - {:requestBody (openapi/openapi-spec - {::openapi/content (into {} + {:requestBody (openapi/openapi-spec + {::openapi/content (merge + (when-let [default (get-in parameters [:request :body])] + (zipmap content-types (repeat (coercion/-compile-model this default nil)))) + (into {} (for [[format model] (:content (:request parameters))] - [format (coercion/-compile-model this model nil)]))})}) + [format (coercion/-compile-model this model nil)])))})}) (when responses {:responses (into (empty responses) - (for [[k response] responses] + (for [[k {:keys [body content] :as response}] responses] [k (merge (select-keys response [:description]) - (when (:body response) + (when (or body content) (openapi/openapi-spec - {::openapi/content {"application/json" (coercion/-compile-model this (:body response) nil)}})) - (when (:content response) - (openapi/openapi-spec - {::openapi/content (into {} - (for [[format model] (:content response)] - [format (coercion/-compile-model this model nil)]))})))]))}))) + {::openapi/content (merge + (when body + (zipmap content-types (repeat (coercion/-compile-model this (:body response) nil)))) + (when response + (into {} + (for [[format model] (:content response)] + [format (coercion/-compile-model this model nil)]))))})))]))}))) (throw (ex-info (str "Can't produce Spec apidocs for " specification) diff --git a/test/cljc/reitit/openapi_test.clj b/test/cljc/reitit/openapi_test.clj index dab5f798..0f999a01 100644 --- a/test/cljc/reitit/openapi_test.clj +++ b/test/cljc/reitit/openapi_test.clj @@ -2,6 +2,7 @@ (:require [clojure.test :refer [deftest is testing]] [jsonista.core :as j] [matcher-combinators.test :refer [match?]] + [matcher-combinators.matchers :as matchers] [muuntaja.core :as m] [reitit.coercion.malli :as malli] [reitit.coercion.schema :as schema] @@ -457,3 +458,43 @@ (app (assoc query :body-params {:z 1})) (catch clojure.lang.ExceptionInfo e (select-keys (ex-data e) [:type :in])))))))))))) + +(deftest default-content-type-test + (doseq [[coercion ->schema] + [[#'malli/coercion (fn [nom] [:map [nom :string]])] + [#'schema/coercion (fn [nom] {nom s/Str})] + [#'spec/coercion (fn [nom] {nom string?})]]] + (testing coercion + (doseq [content-type ["application/json" "application/edn"]] + (testing (str "default content type " content-type) + (let [app (ring/ring-handler + (ring/router + [["/parameters" + {:post {:coercion @coercion + :content-types [content-type] ;; TODO should this be under :openapi ? + :parameters {:request {:content {"application/transit" (->schema :transit)} + :body (->schema :default)}} + :responses {200 {:content {"application/transit" (->schema :transit)} + :body (->schema :default)}} + :handler (fn [req] + {:status 200 + :body (-> req :parameters :request)})}}] + ["/openapi.json" + {:get {:handler (openapi/create-openapi-handler) + :no-doc true}}]] + {:data {:middleware [rrc/coerce-request-middleware + rrc/coerce-response-middleware]}})) + spec (-> {:request-method :get + :uri "/openapi.json"} + app + :body)] + (testing "body parameter" + (is (match? (matchers/in-any-order [content-type "application/transit"]) + (-> spec + (get-in [:paths "/parameters" :post :requestBody :content]) + keys)))) + (testing "body response" + (is (match? (matchers/in-any-order [content-type "application/transit"]) + (-> spec + (get-in [:paths "/parameters" :post :responses 200 :content]) + keys)))))))))) From df0d4c4935c019857049ab98c92bc93904c21488 Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Mon, 6 Mar 2023 16:09:29 +0200 Subject: [PATCH 09/50] chore: upgrade node --- .github/workflows/testsuite.yml | 2 +- package-lock.json | 3691 ++++++++++++++++++------------- 2 files changed, 2178 insertions(+), 1515 deletions(-) diff --git a/.github/workflows/testsuite.yml b/.github/workflows/testsuite.yml index 85e86658..d2f0105b 100644 --- a/.github/workflows/testsuite.yml +++ b/.github/workflows/testsuite.yml @@ -60,7 +60,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v2.1.2 with: - node-version: 12 + node-version: 18 - name: Install dependencies run: | npm ci diff --git a/package-lock.json b/package-lock.json index 98e0c6d9..0cbce24f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,126 +1,161 @@ { "name": "reitit", + "lockfileVersion": 3, "requires": true, - "lockfileVersion": 1, - "dependencies": { - "accepts": { + "packages": { + "": { + "name": "reitit", + "devDependencies": { + "karma": "^4.1.0", + "karma-chrome-launcher": "^2.2.0", + "karma-cli": "^2.0.0", + "karma-cljs-test": "^0.1.0", + "karma-junit-reporter": "^1.2.0" + } + }, + "node_modules/accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", "dev": true, - "requires": { + "dependencies": { "mime-types": "~2.1.24", "negotiator": "0.6.2" + }, + "engines": { + "node": ">= 0.6" } }, - "after": { + "node_modules/after": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=", "dev": true }, - "anymatch": { + "node_modules/anymatch": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", "dev": true, - "requires": { + "dependencies": { "micromatch": "^3.1.4", "normalize-path": "^2.1.1" - }, - "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - } } }, - "arr-diff": { + "node_modules/anymatch/node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-diff": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "arr-flatten": { + "node_modules/arr-flatten": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "arr-union": { + "node_modules/arr-union": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "array-unique": { + "node_modules/array-unique": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "arraybuffer.slice": { + "node_modules/arraybuffer.slice": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==", "dev": true }, - "assign-symbols": { + "node_modules/assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "async": { + "node_modules/async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", "dev": true, - "requires": { + "dependencies": { "lodash": "^4.17.11" } }, - "async-each": { + "node_modules/async-each": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", "dev": true }, - "async-limiter": { + "node_modules/async-limiter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", "dev": true }, - "atob": { + "node_modules/atob": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true + "dev": true, + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } }, - "backo2": { + "node_modules/backo2": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=", "dev": true }, - "balanced-match": { + "node_modules/balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, - "base": { + "node_modules/base": { "version": "0.11.2", "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", "dev": true, - "requires": { + "dependencies": { "cache-base": "^1.0.1", "class-utils": "^0.3.5", "component-emitter": "^1.2.1", @@ -129,98 +164,126 @@ "mixin-deep": "^1.2.0", "pascalcase": "^0.1.1" }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } + "engines": { + "node": ">=0.10.0" } }, - "base64-arraybuffer": { + "node_modules/base/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base64-arraybuffer": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=", - "dev": true + "dev": true, + "engines": { + "node": ">= 0.6.0" + } }, - "base64id": { + "node_modules/base64id": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=", - "dev": true + "dev": true, + "engines": { + "node": ">= 0.4.0" + } }, - "better-assert": { + "node_modules/better-assert": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", "dev": true, - "requires": { + "dependencies": { "callsite": "1.0.0" + }, + "engines": { + "node": "*" } }, - "binary-extensions": { + "node_modules/binary-extensions": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "blob": { + "node_modules/blob": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==", "dev": true }, - "bluebird": { + "node_modules/bluebird": { "version": "3.5.5", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==", "dev": true }, - "body-parser": { + "node_modules/body-parser": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", "dev": true, - "requires": { + "dependencies": { "bytes": "3.1.0", "content-type": "~1.0.4", "debug": "2.6.9", @@ -231,24 +294,27 @@ "qs": "6.7.0", "raw-body": "2.4.0", "type-is": "~1.6.17" + }, + "engines": { + "node": ">= 0.8" } }, - "brace-expansion": { + "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "requires": { + "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, - "braces": { + "node_modules/braces": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, - "requires": { + "dependencies": { "arr-flatten": "^1.1.0", "array-unique": "^0.3.2", "extend-shallow": "^2.0.1", @@ -259,42 +325,48 @@ "snapdragon-node": "^2.0.1", "split-string": "^3.0.2", "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" } }, - "buffer-alloc": { + "node_modules/buffer-alloc": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", "dev": true, - "requires": { + "dependencies": { "buffer-alloc-unsafe": "^1.1.0", "buffer-fill": "^1.0.0" } }, - "buffer-alloc-unsafe": { + "node_modules/buffer-alloc-unsafe": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", "dev": true }, - "buffer-fill": { + "node_modules/buffer-fill": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=", "dev": true }, - "bytes": { + "node_modules/bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", - "dev": true + "dev": true, + "engines": { + "node": ">= 0.8" + } }, - "cache-base": { + "node_modules/cache-base": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", "dev": true, - "requires": { + "dependencies": { "collection-visit": "^1.0.0", "component-emitter": "^1.2.1", "get-value": "^2.0.6", @@ -304,24 +376,30 @@ "to-object-path": "^0.3.0", "union-value": "^1.0.0", "unset-value": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "callsite": { + "node_modules/callsite": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=", - "dev": true + "dev": true, + "engines": { + "node": "*" + } }, - "chokidar": { + "node_modules/chokidar": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.6.tgz", "integrity": "sha512-V2jUo67OKkc6ySiRpJrjlpJKl9kDuG+Xb8VgsGzb+aEouhgS1D0weyPU4lEzdAcsCAvrih2J2BqyXqHWvVLw5g==", + "deprecated": "Chokidar 2 does not receive security updates since 2019. Upgrade to chokidar 3 with 15x fewer dependencies", "dev": true, - "requires": { + "dependencies": { "anymatch": "^2.0.0", "async-each": "^1.0.1", "braces": "^2.3.2", - "fsevents": "^1.2.7", "glob-parent": "^3.1.0", "inherits": "^2.0.3", "is-binary-path": "^1.0.0", @@ -330,205 +408,236 @@ "path-is-absolute": "^1.0.0", "readdirp": "^2.2.1", "upath": "^1.1.1" + }, + "optionalDependencies": { + "fsevents": "^1.2.7" } }, - "class-utils": { + "node_modules/class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", "dev": true, - "requires": { + "dependencies": { "arr-union": "^3.1.0", "define-property": "^0.2.5", "isobject": "^3.0.0", "static-extend": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" } }, - "collection-visit": { + "node_modules/collection-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", "dev": true, - "requires": { + "dependencies": { "map-visit": "^1.0.0", "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "colors": { + "node_modules/colors": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.3.tgz", "integrity": "sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==", - "dev": true + "dev": true, + "engines": { + "node": ">=0.1.90" + } }, - "component-bind": { + "node_modules/component-bind": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=", "dev": true }, - "component-emitter": { + "node_modules/component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", "dev": true }, - "component-inherit": { + "node_modules/component-inherit": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=", "dev": true }, - "concat-map": { + "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, - "connect": { + "node_modules/connect": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", "dev": true, - "requires": { + "dependencies": { "debug": "2.6.9", "finalhandler": "1.1.2", "parseurl": "~1.3.3", "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10.0" } }, - "content-type": { + "node_modules/content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", - "dev": true + "dev": true, + "engines": { + "node": ">= 0.6" + } }, - "cookie": { + "node_modules/cookie": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", - "dev": true + "dev": true, + "engines": { + "node": ">= 0.6" + } }, - "copy-descriptor": { + "node_modules/copy-descriptor": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "core-js": { + "node_modules/core-js": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz", "integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==", - "dev": true + "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", + "dev": true, + "hasInstallScript": true }, - "core-util-is": { + "node_modules/core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true }, - "custom-event": { + "node_modules/custom-event": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", "dev": true }, - "date-format": { + "node_modules/date-format": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.0.0.tgz", "integrity": "sha512-M6UqVvZVgFYqZL1SfHsRGIQSz3ZL+qgbsV5Lp1Vj61LZVYuEwcMXYay7DRDtYs2HQQBK5hQtQ0fD9aEJ89V0LA==", - "dev": true + "deprecated": "2.x is no longer supported. Please upgrade to 4.x or higher.", + "dev": true, + "engines": { + "node": ">=4.0" + } }, - "debug": { + "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, - "requires": { + "dependencies": { "ms": "2.0.0" } }, - "decode-uri-component": { + "node_modules/decode-uri-component": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10" + } }, - "define-property": { + "node_modules/define-property": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, - "requires": { + "dependencies": { "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "depd": { + "node_modules/depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", - "dev": true + "dev": true, + "engines": { + "node": ">= 0.6" + } }, - "di": { + "node_modules/di": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", "dev": true }, - "dom-serialize": { + "node_modules/dom-serialize": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", "dev": true, - "requires": { + "dependencies": { "custom-event": "~1.0.0", "ent": "~2.2.0", "extend": "^3.0.0", "void-elements": "^2.0.0" } }, - "ee-first": { + "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", "dev": true }, - "encodeurl": { + "node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", - "dev": true + "dev": true, + "engines": { + "node": ">= 0.8" + } }, - "engine.io": { + "node_modules/engine.io": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.2.1.tgz", "integrity": "sha512-+VlKzHzMhaU+GsCIg4AoXF1UdDFjHHwMmMKqMJNDNLlUlejz58FCy4LBqB2YVJskHGYl06BatYWKP2TVdVXE5w==", "dev": true, - "requires": { + "dependencies": { "accepts": "~1.3.4", "base64id": "1.0.0", "cookie": "0.3.1", "debug": "~3.1.0", "engine.io-parser": "~2.1.0", "ws": "~3.3.1" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } } }, - "engine.io-client": { + "node_modules/engine.io-client": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz", "integrity": "sha512-y5AbkytWeM4jQr7m/koQLc5AxpRKC1hEVUb/s1FUAWEJq5AzJJ4NLvzuKPuxtDi5Mq755WuDvZ6Iv2rXj4PTzw==", "dev": true, - "requires": { + "dependencies": { "component-emitter": "1.2.1", "component-inherit": "0.0.3", "debug": "~3.1.0", @@ -540,31 +649,29 @@ "ws": "~3.3.1", "xmlhttprequest-ssl": "~1.5.4", "yeast": "0.1.2" - }, - "dependencies": { - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", - "dev": true - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } } }, - "engine.io-parser": { + "node_modules/engine.io-client/node_modules/component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, + "node_modules/engine.io-client/node_modules/debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/engine.io-parser": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.3.tgz", "integrity": "sha512-6HXPre2O4Houl7c4g7Ic/XzPnHBvaEmN90vtRO9uLmwtRqQmTOw0QMevL1TOfL2Cpu1VzsaTmMotQgMdkzGkVA==", "dev": true, - "requires": { + "dependencies": { "after": "0.8.2", "arraybuffer.slice": "~0.0.7", "base64-arraybuffer": "0.1.5", @@ -572,30 +679,39 @@ "has-binary2": "~1.0.2" } }, - "ent": { + "node_modules/engine.io/node_modules/debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/ent": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", "dev": true }, - "escape-html": { + "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", "dev": true }, - "eventemitter3": { + "node_modules/eventemitter3": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==", "dev": true }, - "expand-brackets": { + "node_modules/expand-brackets": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", "dev": true, - "requires": { + "dependencies": { "debug": "^2.3.3", "define-property": "^0.2.5", "extend-shallow": "^2.0.1", @@ -603,29 +719,35 @@ "regex-not": "^1.0.0", "snapdragon": "^0.8.1", "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" } }, - "extend": { + "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "dev": true }, - "extend-shallow": { + "node_modules/extend-shallow": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, - "requires": { + "dependencies": { "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "extglob": { + "node_modules/extglob": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", "dev": true, - "requires": { + "dependencies": { "array-unique": "^0.3.2", "define-property": "^1.0.0", "expand-brackets": "^2.1.4", @@ -635,71 +757,90 @@ "snapdragon": "^0.8.1", "to-regex": "^3.0.1" }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } + "engines": { + "node": ">=0.10.0" } }, - "fill-range": { + "node_modules/extglob/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fill-range": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, - "requires": { + "dependencies": { "extend-shallow": "^2.0.1", "is-number": "^3.0.0", "repeat-string": "^1.6.1", "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "finalhandler": { + "node_modules/finalhandler": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", "dev": true, - "requires": { + "dependencies": { "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", @@ -707,925 +848,1158 @@ "parseurl": "~1.3.3", "statuses": "~1.5.0", "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" } }, - "flatted": { + "node_modules/flatted": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.0.tgz", "integrity": "sha512-R+H8IZclI8AAkSBRQJLVOsxwAoHd6WC40b4QTNWIjzAa6BXOBfQcM587MXDTVPeYaopFNWHUFLx7eNmHDSxMWg==", "dev": true }, - "follow-redirects": { + "node_modules/follow-redirects": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.7.0.tgz", "integrity": "sha512-m/pZQy4Gj287eNy94nivy5wchN3Kp+Q5WgUPNy5lJSZ3sgkVKSYV/ZChMAQVIgx1SqfZ2zBZtPA2YlXIWxxJOQ==", "dev": true, - "requires": { + "dependencies": { "debug": "^3.2.6" }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } + "engines": { + "node": ">=4.0" } }, - "for-in": { + "node_modules/follow-redirects/node_modules/debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/follow-redirects/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "fragment-cache": { + "node_modules/fragment-cache": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", "dev": true, - "requires": { + "dependencies": { "map-cache": "^0.2.2" + }, + "engines": { + "node": ">=0.10.0" } }, - "fs-access": { + "node_modules/fs-access": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz", "integrity": "sha1-1qh/JiJxzv6+wwxVNAf7mV2od3o=", "dev": true, - "requires": { + "dependencies": { "null-check": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "fs-extra": { + "node_modules/fs-extra": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", "dev": true, - "requires": { + "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" } }, - "fs.realpath": { + "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, - "fsevents": { + "node_modules/fsevents": { "version": "1.2.9", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", + "bundleDependencies": [ + "node-pre-gyp" + ], + "deprecated": "fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.", "dev": true, + "hasInstallScript": true, "optional": true, - "requires": { + "os": [ + "darwin" + ], + "dependencies": { "nan": "^2.12.1", "node-pre-gyp": "^0.12.0" }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "aproba": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "debug": { - "version": "4.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ms": "^2.1.1" - } - }, - "deep-extend": { - "version": "0.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "detect-libc": { - "version": "1.0.3", - "bundled": true, - "dev": true, - "optional": true - }, - "fs-minipass": { - "version": "1.2.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.2.1" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "glob": { - "version": "7.1.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "iconv-lite": { - "version": "0.4.24", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore-walk": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.3", - "bundled": true, - "dev": true, - "optional": true - }, - "ini": { - "version": "1.3.5", - "bundled": true, - "dev": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "bundled": true, - "dev": true, - "optional": true - }, - "minipass": { - "version": "2.3.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.2.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.2.1" - } - }, - "mkdirp": { - "version": "0.5.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "needle": { - "version": "2.3.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "debug": "^4.1.0", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.12.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4" - } - }, - "nopt": { - "version": "4.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "npm-bundled": { - "version": "1.0.6", - "bundled": true, - "dev": true, - "optional": true - }, - "npm-packlist": { - "version": "1.4.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" - } - }, - "npmlog": { - "version": "4.1.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "wrappy": "1" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "osenv": { - "version": "0.1.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "process-nextick-args": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "rc": { - "version": "1.2.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "readable-stream": { - "version": "2.3.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "rimraf": { - "version": "2.6.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "glob": "^7.1.3" - } - }, - "safe-buffer": { - "version": "5.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "safer-buffer": { - "version": "2.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "sax": { - "version": "1.2.4", - "bundled": true, - "dev": true, - "optional": true - }, - "semver": { - "version": "5.7.0", - "bundled": true, - "dev": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "tar": { - "version": "4.4.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.3.4", - "minizlib": "^1.1.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.2" - } - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "wide-align": { - "version": "1.1.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "yallist": { - "version": "3.0.3", - "bundled": true, - "dev": true, - "optional": true - } + "engines": { + "node": ">=4.0" } }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true - }, - "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "node_modules/fsevents/node_modules/abbrev": { + "version": "1.1.1", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true, - "requires": { + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/ansi-regex": { + "version": "2.1.1", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, + "inBundle": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/aproba": { + "version": "1.2.0", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/are-we-there-yet": { + "version": "1.1.5", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "node_modules/fsevents/node_modules/balanced-match": { + "version": "1.0.0", + "integrity": "sha512-9Y0g0Q8rmSt+H33DfKv7FOc3v+iRI+o1lbzt8jGcIosYW37IIW/2XVYq5NPdmaD5NQ59Nk26Kl/vZbwW9Fr8vg==", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/brace-expansion": { + "version": "1.1.11", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/fsevents/node_modules/chownr": { + "version": "1.1.1", + "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/code-point-at": { + "version": "1.1.0", + "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==", + "dev": true, + "inBundle": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/concat-map": { + "version": "0.0.1", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/console-control-strings": { + "version": "1.1.0", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/core-util-is": { + "version": "1.0.2", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/debug": { + "version": "4.1.1", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/fsevents/node_modules/deep-extend": { + "version": "0.6.0", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "inBundle": true, + "optional": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/fsevents/node_modules/delegates": { + "version": "1.0.0", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/detect-libc": { + "version": "1.0.3", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "inBundle": true, + "optional": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/fsevents/node_modules/fs-minipass": { + "version": "1.2.5", + "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "minipass": "^2.2.1" + } + }, + "node_modules/fsevents/node_modules/fs.realpath": { + "version": "1.0.0", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/gauge": { + "version": "2.7.4", + "integrity": "sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "node_modules/fsevents/node_modules/glob": { + "version": "7.1.3", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.0.4", "once": "^1.3.0", "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" } }, - "glob-parent": { + "node_modules/fsevents/node_modules/has-unicode": { + "version": "2.0.1", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/iconv-lite": { + "version": "0.4.24", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/ignore-walk": { + "version": "3.0.1", + "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "minimatch": "^3.0.4" + } + }, + "node_modules/fsevents/node_modules/inflight": { + "version": "1.0.6", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/fsevents/node_modules/inherits": { + "version": "2.0.3", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/ini": { + "version": "1.3.5", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "deprecated": "Please update to ini >=1.3.6 to avoid a prototype pollution issue", + "dev": true, + "inBundle": true, + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/fsevents/node_modules/is-fullwidth-code-point": { + "version": "1.0.0", + "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "number-is-nan": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/isarray": { + "version": "1.0.0", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/minimatch": { + "version": "3.0.4", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/fsevents/node_modules/minimist": { + "version": "0.0.8", + "integrity": "sha512-miQKw5Hv4NS1Psg2517mV4e4dYNaO3++hjAvLOAzKqZ61rH8NS1SK+vbfBWZ5PY/Me/bEWhUwqMghEW5Fb9T7Q==", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/minipass": { + "version": "2.3.5", + "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "node_modules/fsevents/node_modules/minizlib": { + "version": "1.2.1", + "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "minipass": "^2.2.1" + } + }, + "node_modules/fsevents/node_modules/mkdirp": { + "version": "0.5.1", + "integrity": "sha512-SknJC52obPfGQPnjIkXbmA6+5H15E+fR+E4iR2oQ3zzCLbd7/ONua69R/Gw7AgkTLsRG+r5fzksYwWe1AgTyWA==", + "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "minimist": "0.0.8" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/fsevents/node_modules/ms": { + "version": "2.1.1", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/needle": { + "version": "2.3.0", + "integrity": "sha512-QBZu7aAFR0522EyaXZM0FZ9GLpq6lvQ3uq8gteiDUp7wKdy0lSd2hPlgFwVuW1CBkfEs9PfDQsQzZghLs/psdg==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "debug": "^4.1.0", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/fsevents/node_modules/node-pre-gyp": { + "version": "0.12.0", + "integrity": "sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==", + "deprecated": "Please upgrade to @mapbox/node-pre-gyp: the non-scoped node-pre-gyp package is deprecated and only the @mapbox scoped package will recieve updates in the future", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/fsevents/node_modules/nopt": { + "version": "4.0.1", + "integrity": "sha512-+5XZFpQZEY0cg5JaxLwGxDlKNKYxuXwGt8/Oi3UXm5/4ymrJve9d2CURituxv3rSrVCGZj4m1U1JlHTdcKt2Ng==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "abbrev": "1", + "osenv": "^0.1.4" + }, + "bin": { + "nopt": "bin/nopt.js" + } + }, + "node_modules/fsevents/node_modules/npm-bundled": { + "version": "1.0.6", + "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/npm-packlist": { + "version": "1.4.1", + "integrity": "sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "node_modules/fsevents/node_modules/npmlog": { + "version": "4.1.2", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "node_modules/fsevents/node_modules/number-is-nan": { + "version": "1.0.1", + "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==", + "dev": true, + "inBundle": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/object-assign": { + "version": "4.1.1", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "inBundle": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/once": { + "version": "1.4.0", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/fsevents/node_modules/os-homedir": { + "version": "1.0.2", + "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", + "dev": true, + "inBundle": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/os-tmpdir": { + "version": "1.0.2", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true, + "inBundle": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/osenv": { + "version": "0.1.5", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "node_modules/fsevents/node_modules/path-is-absolute": { + "version": "1.0.1", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "inBundle": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/process-nextick-args": { + "version": "2.0.0", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/rc": { + "version": "1.2.8", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/fsevents/node_modules/rc/node_modules/minimist": { + "version": "1.2.0", + "integrity": "sha512-7Wl+Jz+IGWuSdgsQEJ4JunV0si/iMhg42MnQQG6h1R6TNeVenp4U9x5CC5v/gYqz/fENLQITAWXidNtVL0NNbw==", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/readable-stream": { + "version": "2.3.6", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/fsevents/node_modules/rimraf": { + "version": "2.6.3", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/fsevents/node_modules/safe-buffer": { + "version": "5.1.2", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/safer-buffer": { + "version": "2.1.2", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/sax": { + "version": "1.2.4", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/semver": { + "version": "5.7.0", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true, + "inBundle": true, + "optional": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/fsevents/node_modules/set-blocking": { + "version": "2.0.0", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/signal-exit": { + "version": "3.0.2", + "integrity": "sha512-meQNNykwecVxdu1RlYMKpQx4+wefIYpmxi6gexo/KAbwquJrBUrBmKYJrE8KFkVQAAVWEnwNdu21PgrD77J3xA==", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/string_decoder": { + "version": "1.1.1", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/fsevents/node_modules/string-width": { + "version": "1.0.2", + "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/strip-ansi": { + "version": "3.0.1", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/strip-json-comments": { + "version": "2.0.1", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "inBundle": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/tar": { + "version": "4.4.8", + "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + }, + "engines": { + "node": ">=4.5" + } + }, + "node_modules/fsevents/node_modules/util-deprecate": { + "version": "1.0.2", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/wide-align": { + "version": "1.1.3", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "string-width": "^1.0.2 || 2" + } + }, + "node_modules/fsevents/node_modules/wrappy": { + "version": "1.0.2", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/yallist": { + "version": "3.0.3", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/glob-parent": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", "dev": true, - "requires": { + "dependencies": { "is-glob": "^3.1.0", "path-dirname": "^1.0.0" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "requires": { - "is-extglob": "^2.1.0" - } - } } }, - "graceful-fs": { + "node_modules/glob-parent/node_modules/is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/graceful-fs": { "version": "4.1.15", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", "dev": true }, - "has-binary2": { + "node_modules/has-binary2": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", "dev": true, - "requires": { - "isarray": "2.0.1" - }, "dependencies": { - "isarray": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", - "dev": true - } + "isarray": "2.0.1" } }, - "has-cors": { + "node_modules/has-binary2/node_modules/isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", + "dev": true + }, + "node_modules/has-cors": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=", "dev": true }, - "has-value": { + "node_modules/has-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", "dev": true, - "requires": { + "dependencies": { "get-value": "^2.0.6", "has-values": "^1.0.0", "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "has-values": { + "node_modules/has-values": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", "dev": true, - "requires": { + "dependencies": { "is-number": "^3.0.0", "kind-of": "^4.0.0" }, - "dependencies": { - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } + "engines": { + "node": ">=0.10.0" } }, - "http-errors": { + "node_modules/has-values/node_modules/kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/http-errors": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", "dev": true, - "requires": { + "dependencies": { "depd": "~1.1.2", "inherits": "2.0.3", "setprototypeof": "1.1.1", "statuses": ">= 1.5.0 < 2", "toidentifier": "1.0.0" + }, + "engines": { + "node": ">= 0.6" } }, - "http-proxy": { + "node_modules/http-proxy": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz", "integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==", "dev": true, - "requires": { + "dependencies": { "eventemitter3": "^3.0.0", "follow-redirects": "^1.0.0", "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=4.0.0" } }, - "iconv-lite": { + "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, - "requires": { + "dependencies": { "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" } }, - "indexof": { + "node_modules/indexof": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", "dev": true }, - "inflight": { + "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, - "requires": { + "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, - "inherits": { + "node_modules/inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true }, - "is-accessor-descriptor": { + "node_modules/is-accessor-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, - "requires": { + "dependencies": { "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" } }, - "is-binary-path": { + "node_modules/is-binary-path": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", "dev": true, - "requires": { + "dependencies": { "binary-extensions": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "is-buffer": { + "node_modules/is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true }, - "is-data-descriptor": { + "node_modules/is-data-descriptor": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, - "requires": { + "dependencies": { "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" } }, - "is-descriptor": { + "node_modules/is-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", "dev": true, - "requires": { + "dependencies": { "is-accessor-descriptor": "^0.1.6", "is-data-descriptor": "^0.1.4", "kind-of": "^5.0.0" }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } + "engines": { + "node": ">=0.10.0" } }, - "is-extendable": { + "node_modules/is-descriptor/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "is-extglob": { + "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "is-glob": { + "node_modules/is-glob": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", "dev": true, - "requires": { + "dependencies": { "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" } }, - "is-number": { + "node_modules/is-number": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, - "requires": { + "dependencies": { "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" } }, - "is-plain-object": { + "node_modules/is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, - "requires": { + "dependencies": { "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" } }, - "is-windows": { + "node_modules/is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "isarray": { + "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true }, - "isbinaryfile": { + "node_modules/isbinaryfile": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.3.tgz", "integrity": "sha512-8cJBL5tTd2OS0dM4jz07wQd5g0dCCqIhUxPIGtZfa5L6hWlvV5MHTITy/DBAsF+Oe2LS1X3krBUhNwaGUWpWxw==", "dev": true, - "requires": { + "dependencies": { "buffer-alloc": "^1.2.0" + }, + "engines": { + "node": ">=0.6.0" } }, - "isexe": { + "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, - "isobject": { + "node_modules/isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "jsonfile": { + "node_modules/jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", "dev": true, - "requires": { + "optionalDependencies": { "graceful-fs": "^4.1.6" } }, - "karma": { + "node_modules/karma": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/karma/-/karma-4.1.0.tgz", "integrity": "sha512-xckiDqyNi512U4dXGOOSyLKPwek6X/vUizSy2f3geYevbLj+UIdvNwbn7IwfUIL2g1GXEPWt/87qFD1fBbl/Uw==", "dev": true, - "requires": { + "dependencies": { "bluebird": "^3.3.0", "body-parser": "^1.16.1", "braces": "^2.3.2", @@ -1653,125 +2027,155 @@ "source-map": "^0.6.1", "tmp": "0.0.33", "useragent": "2.3.0" + }, + "bin": { + "karma": "bin/karma" + }, + "engines": { + "node": ">= 8" } }, - "karma-chrome-launcher": { + "node_modules/karma-chrome-launcher": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-2.2.0.tgz", "integrity": "sha512-uf/ZVpAabDBPvdPdveyk1EPgbnloPvFFGgmRhYLTDH7gEB4nZdSBk8yTU47w1g/drLSx5uMOkjKk7IWKfWg/+w==", "dev": true, - "requires": { + "dependencies": { "fs-access": "^1.0.0", "which": "^1.2.1" } }, - "karma-cli": { + "node_modules/karma-cli": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/karma-cli/-/karma-cli-2.0.0.tgz", "integrity": "sha512-1Kb28UILg1ZsfqQmeELbPzuEb5C6GZJfVIk0qOr8LNYQuYWmAaqP16WpbpKEjhejDrDYyYOwwJXSZO6u7q5Pvw==", "dev": true, - "requires": { + "dependencies": { "resolve": "^1.3.3" + }, + "bin": { + "karma": "bin/karma" + }, + "engines": { + "node": ">= 6" } }, - "karma-cljs-test": { + "node_modules/karma-cljs-test": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/karma-cljs-test/-/karma-cljs-test-0.1.0.tgz", "integrity": "sha1-y4YF7w4R+ab20o9Wul298m84mSM=", "dev": true }, - "karma-junit-reporter": { + "node_modules/karma-junit-reporter": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/karma-junit-reporter/-/karma-junit-reporter-1.2.0.tgz", "integrity": "sha1-T5xAzt+xo5X4rvh2q/lhiZF8Y5Y=", "dev": true, - "requires": { + "dependencies": { "path-is-absolute": "^1.0.0", "xmlbuilder": "8.2.2" + }, + "peerDependencies": { + "karma": ">=0.9" } }, - "kind-of": { + "node_modules/kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, - "requires": { + "dependencies": { "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" } }, - "lodash": { + "node_modules/lodash": { "version": "4.17.11", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", "dev": true }, - "log4js": { + "node_modules/log4js": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/log4js/-/log4js-4.3.1.tgz", "integrity": "sha512-nPGS7w7kBnzNm1j8JycFxwLCbIMae8tHCo0cCdx/khB20Tcod8SZThYEB9E0c27ObcTGA1mlPowaf3hantQ/FA==", + "deprecated": "4.x is no longer supported. Please upgrade to 6.x or higher.", "dev": true, - "requires": { + "dependencies": { "date-format": "^2.0.0", "debug": "^4.1.1", "flatted": "^2.0.0", "rfdc": "^1.1.2", "streamroller": "^1.0.5" }, - "dependencies": { - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } + "engines": { + "node": ">=6.0" } }, - "lru-cache": { + "node_modules/log4js/node_modules/debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/log4js/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/lru-cache": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", "dev": true, - "requires": { + "dependencies": { "pseudomap": "^1.0.2", "yallist": "^2.1.2" } }, - "map-cache": { + "node_modules/map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "map-visit": { + "node_modules/map-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", "dev": true, - "requires": { + "dependencies": { "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "media-typer": { + "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", - "dev": true + "dev": true, + "engines": { + "node": ">= 0.6" + } }, - "micromatch": { + "node_modules/micromatch": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, - "requires": { + "dependencies": { "arr-diff": "^4.0.0", "array-unique": "^0.3.2", "braces": "^2.3.1", @@ -1786,149 +2190,191 @@ "snapdragon": "^0.8.1", "to-regex": "^3.0.2" }, - "dependencies": { - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - } - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } + "engines": { + "node": ">=0.10.0" } }, - "mime": { + "node_modules/micromatch/node_modules/define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/micromatch/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/micromatch/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/micromatch/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/micromatch/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/micromatch/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/micromatch/node_modules/kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mime": { "version": "2.4.4", "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==", - "dev": true + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } }, - "mime-db": { + "node_modules/mime-db": { "version": "1.40.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==", - "dev": true + "dev": true, + "engines": { + "node": ">= 0.6" + } }, - "mime-types": { + "node_modules/mime-types": { "version": "2.1.24", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", "dev": true, - "requires": { + "dependencies": { "mime-db": "1.40.0" + }, + "engines": { + "node": ">= 0.6" } }, - "minimatch": { + "node_modules/minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, - "requires": { + "dependencies": { "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" } }, - "minimist": { + "node_modules/minimist": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", "dev": true }, - "mixin-deep": { + "node_modules/mixin-deep": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "deprecated": "Critical bug fixed in v2.0.1, please upgrade to the latest version.", "dev": true, - "requires": { + "dependencies": { "for-in": "^1.0.2", "is-extendable": "^1.0.1" }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } + "engines": { + "node": ">=0.10.0" } }, - "ms": { + "node_modules/mixin-deep/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, - "nan": { + "node_modules/nan": { "version": "2.14.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", "dev": true, "optional": true }, - "nanomatch": { + "node_modules/nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", "dev": true, - "requires": { + "dependencies": { "arr-diff": "^4.0.0", "array-unique": "^0.3.2", "define-property": "^2.0.2", @@ -1941,262 +2387,332 @@ "snapdragon": "^0.8.1", "to-regex": "^3.0.1" }, - "dependencies": { - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - } - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } + "engines": { + "node": ">=0.10.0" } }, - "negotiator": { + "node_modules/nanomatch/node_modules/define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nanomatch/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nanomatch/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nanomatch/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nanomatch/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nanomatch/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nanomatch/node_modules/kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", - "dev": true + "dev": true, + "engines": { + "node": ">= 0.6" + } }, - "normalize-path": { + "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "null-check": { + "node_modules/null-check": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/null-check/-/null-check-1.0.0.tgz", "integrity": "sha1-l33/1xdgErnsMNKjnbXPcqBDnt0=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "object-component": { + "node_modules/object-component": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=", "dev": true }, - "object-copy": { + "node_modules/object-copy": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", "dev": true, - "requires": { + "dependencies": { "copy-descriptor": "^0.1.0", "define-property": "^0.2.5", "kind-of": "^3.0.3" + }, + "engines": { + "node": ">=0.10.0" } }, - "object-visit": { + "node_modules/object-visit": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", "dev": true, - "requires": { + "dependencies": { "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "object.pick": { + "node_modules/object.pick": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", "dev": true, - "requires": { + "dependencies": { "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" } }, - "on-finished": { + "node_modules/on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", "dev": true, - "requires": { + "dependencies": { "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" } }, - "once": { + "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, - "requires": { + "dependencies": { "wrappy": "1" } }, - "optimist": { + "node_modules/optimist": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", "dev": true, - "requires": { + "dependencies": { "minimist": "~0.0.1", "wordwrap": "~0.0.2" } }, - "os-tmpdir": { + "node_modules/os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "parseqs": { + "node_modules/parseqs": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", "dev": true, - "requires": { + "dependencies": { "better-assert": "~1.0.0" } }, - "parseuri": { + "node_modules/parseuri": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", "dev": true, - "requires": { + "dependencies": { "better-assert": "~1.0.0" } }, - "parseurl": { + "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true + "dev": true, + "engines": { + "node": ">= 0.8" + } }, - "pascalcase": { + "node_modules/pascalcase": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "path-dirname": { + "node_modules/path-dirname": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", "dev": true }, - "path-is-absolute": { + "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "path-parse": { + "node_modules/path-parse": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", "dev": true }, - "posix-character-classes": { + "node_modules/posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "process-nextick-args": { + "node_modules/process-nextick-args": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", "dev": true }, - "pseudomap": { + "node_modules/pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", "dev": true }, - "qjobs": { + "node_modules/qjobs": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", - "dev": true + "dev": true, + "engines": { + "node": ">=0.9" + } }, - "qs": { + "node_modules/qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", - "dev": true + "dev": true, + "engines": { + "node": ">=0.6" + } }, - "range-parser": { + "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true + "dev": true, + "engines": { + "node": ">= 0.6" + } }, - "raw-body": { + "node_modules/raw-body": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", "dev": true, - "requires": { + "dependencies": { "bytes": "3.1.0", "http-errors": "1.7.2", "iconv-lite": "0.4.24", "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" } }, - "readable-stream": { + "node_modules/readable-stream": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, - "requires": { + "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", @@ -2206,153 +2722,180 @@ "util-deprecate": "~1.0.1" } }, - "readdirp": { + "node_modules/readdirp": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", "dev": true, - "requires": { + "dependencies": { "graceful-fs": "^4.1.11", "micromatch": "^3.1.10", "readable-stream": "^2.0.2" + }, + "engines": { + "node": ">=0.10" } }, - "regex-not": { + "node_modules/regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", "dev": true, - "requires": { + "dependencies": { "extend-shallow": "^3.0.2", "safe-regex": "^1.1.0" }, - "dependencies": { - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - } - }, - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } + "engines": { + "node": ">=0.10.0" } }, - "remove-trailing-separator": { + "node_modules/regex-not/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/regex-not/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", "dev": true }, - "repeat-element": { + "node_modules/repeat-element": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "repeat-string": { + "node_modules/repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10" + } }, - "requires-port": { + "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", "dev": true }, - "resolve": { + "node_modules/resolve": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.1.tgz", "integrity": "sha512-vIpgF6wfuJOZI7KKKSP+HmiKggadPQAdsp5HiC1mvqnfp0gF1vdwgBWZIdrVft9pgqoMFQN+R7BSWZiBxx+BBw==", "dev": true, - "requires": { + "dependencies": { "path-parse": "^1.0.6" } }, - "resolve-url": { + "node_modules/resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "deprecated": "https://github.com/lydell/resolve-url#deprecated", "dev": true }, - "ret": { + "node_modules/ret": { "version": "0.1.15", "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true + "dev": true, + "engines": { + "node": ">=0.12" + } }, - "rfdc": { + "node_modules/rfdc": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.1.4.tgz", "integrity": "sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug==", "dev": true }, - "rimraf": { + "node_modules/rimraf": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "dev": true, - "requires": { + "dependencies": { "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" } }, - "safe-buffer": { + "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, - "safe-regex": { + "node_modules/safe-regex": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "dev": true, - "requires": { + "dependencies": { "ret": "~0.1.10" } }, - "safer-buffer": { + "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, - "set-value": { + "node_modules/set-value": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", + "deprecated": "Critical bug fixed in v3.0.1, please upgrade to the latest version.", "dev": true, - "requires": { + "dependencies": { "extend-shallow": "^2.0.1", "is-extendable": "^0.1.1", "is-plain-object": "^2.0.3", "split-string": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" } }, - "setprototypeof": { + "node_modules/setprototypeof": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", "dev": true }, - "snapdragon": { + "node_modules/snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", "dev": true, - "requires": { + "dependencies": { "base": "^0.11.1", "debug": "^2.2.0", "define-property": "^0.2.5", @@ -2362,118 +2905,130 @@ "source-map-resolve": "^0.5.0", "use": "^3.1.0" }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } + "engines": { + "node": ">=0.10.0" } }, - "snapdragon-node": { + "node_modules/snapdragon-node": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", "dev": true, - "requires": { + "dependencies": { "define-property": "^1.0.0", "isobject": "^3.0.0", "snapdragon-util": "^3.0.1" }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } + "engines": { + "node": ">=0.10.0" } }, - "snapdragon-util": { + "node_modules/snapdragon-node/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-util": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", "dev": true, - "requires": { + "dependencies": { "kind-of": "^3.2.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "socket.io": { + "node_modules/snapdragon/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/socket.io": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.1.1.tgz", "integrity": "sha512-rORqq9c+7W0DAK3cleWNSyfv/qKXV99hV4tZe+gGLfBECw3XEhBy7x85F3wypA9688LKjtwO9pX9L33/xQI8yA==", "dev": true, - "requires": { + "dependencies": { "debug": "~3.1.0", "engine.io": "~3.2.0", "has-binary2": "~1.0.2", "socket.io-adapter": "~1.1.0", "socket.io-client": "2.1.1", "socket.io-parser": "~3.2.0" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } } }, - "socket.io-adapter": { + "node_modules/socket.io-adapter": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz", "integrity": "sha1-KoBeihTWNyEk3ZFZrUUC+MsH8Gs=", "dev": true }, - "socket.io-client": { + "node_modules/socket.io-client": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.1.1.tgz", "integrity": "sha512-jxnFyhAuFxYfjqIgduQlhzqTcOEQSn+OHKVfAxWaNWa7ecP7xSNk2Dx/3UEsDcY7NcFafxvNvKPmmO7HTwTxGQ==", "dev": true, - "requires": { + "dependencies": { "backo2": "1.0.2", "base64-arraybuffer": "0.1.5", "component-bind": "1.0.0", @@ -2488,71 +3043,80 @@ "parseuri": "0.0.5", "socket.io-parser": "~3.2.0", "to-array": "0.1.4" - }, - "dependencies": { - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", - "dev": true - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } } }, - "socket.io-parser": { + "node_modules/socket.io-client/node_modules/component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, + "node_modules/socket.io-client/node_modules/debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/socket.io-parser": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz", "integrity": "sha512-FYiBx7rc/KORMJlgsXysflWx/RIvtqZbyGLlHZvjfmPTPeuD/I8MaW7cfFrj5tRltICJdgwflhfZ3NVVbVLFQA==", "dev": true, - "requires": { + "dependencies": { "component-emitter": "1.2.1", "debug": "~3.1.0", "isarray": "2.0.1" - }, - "dependencies": { - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", - "dev": true - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "isarray": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", - "dev": true - } } }, - "source-map": { + "node_modules/socket.io-parser/node_modules/component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", + "dev": true + }, + "node_modules/socket.io/node_modules/debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "source-map-resolve": { + "node_modules/source-map-resolve": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", "dev": true, - "requires": { + "dependencies": { "atob": "^2.1.1", "decode-uri-component": "^0.2.0", "resolve-url": "^0.2.1", @@ -2560,406 +3124,505 @@ "urix": "^0.1.0" } }, - "source-map-url": { + "node_modules/source-map-url": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "deprecated": "See https://github.com/lydell/source-map-url#deprecated", "dev": true }, - "split-string": { + "node_modules/split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", "dev": true, - "requires": { + "dependencies": { "extend-shallow": "^3.0.0" }, - "dependencies": { - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - } - }, - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } + "engines": { + "node": ">=0.10.0" } }, - "static-extend": { + "node_modules/split-string/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split-string/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", "dev": true, - "requires": { + "dependencies": { "define-property": "^0.2.5", "object-copy": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "statuses": { + "node_modules/statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", - "dev": true + "dev": true, + "engines": { + "node": ">= 0.6" + } }, - "streamroller": { + "node_modules/streamroller": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-1.0.5.tgz", "integrity": "sha512-iGVaMcyF5PcUY0cPbW3xFQUXnr9O4RZXNBBjhuLZgrjLO4XCLLGfx4T2sGqygSeylUjwgWRsnNbT9aV0Zb8AYw==", + "deprecated": "1.x is no longer supported. Please upgrade to 3.x or higher.", "dev": true, - "requires": { + "dependencies": { "async": "^2.6.2", "date-format": "^2.0.0", "debug": "^3.2.6", "fs-extra": "^7.0.1", "lodash": "^4.17.11" }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } + "engines": { + "node": ">=6.0" } }, - "string_decoder": { + "node_modules/streamroller/node_modules/debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/streamroller/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, - "requires": { + "dependencies": { "safe-buffer": "~5.1.0" } }, - "tmp": { + "node_modules/tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", "dev": true, - "requires": { + "dependencies": { "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" } }, - "to-array": { + "node_modules/to-array": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=", "dev": true }, - "to-object-path": { + "node_modules/to-object-path": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", "dev": true, - "requires": { + "dependencies": { "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" } }, - "to-regex": { + "node_modules/to-regex": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", "dev": true, - "requires": { + "dependencies": { "define-property": "^2.0.2", "extend-shallow": "^3.0.2", "regex-not": "^1.0.2", "safe-regex": "^1.1.0" }, - "dependencies": { - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - } - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } + "engines": { + "node": ">=0.10.0" } }, - "to-regex-range": { + "node_modules/to-regex-range": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", "dev": true, - "requires": { + "dependencies": { "is-number": "^3.0.0", "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" } }, - "toidentifier": { + "node_modules/to-regex/node_modules/define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex/node_modules/kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/toidentifier": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", - "dev": true + "dev": true, + "engines": { + "node": ">=0.6" + } }, - "type-is": { + "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "dev": true, - "requires": { + "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" } }, - "ultron": { + "node_modules/ultron": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", "dev": true }, - "union-value": { + "node_modules/union-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", "dev": true, - "requires": { + "dependencies": { "arr-union": "^3.1.0", "get-value": "^2.0.6", "is-extendable": "^0.1.1", "set-value": "^0.4.3" }, - "dependencies": { - "set-value": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", - "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.1", - "to-object-path": "^0.3.0" - } - } + "engines": { + "node": ">=0.10.0" } }, - "universalify": { + "node_modules/union-value/node_modules/set-value": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", + "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", + "deprecated": "Critical bug fixed in v3.0.1, please upgrade to the latest version.", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.1", + "to-object-path": "^0.3.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true + "dev": true, + "engines": { + "node": ">= 4.0.0" + } }, - "unpipe": { + "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", - "dev": true + "dev": true, + "engines": { + "node": ">= 0.8" + } }, - "unset-value": { + "node_modules/unset-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", "dev": true, - "requires": { + "dependencies": { "has-value": "^0.3.1", "isobject": "^3.0.0" }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true - } + "engines": { + "node": ">=0.10.0" } }, - "upath": { + "node_modules/unset-value/node_modules/has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "dependencies": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "dependencies": { + "isarray": "1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/upath": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.2.tgz", "integrity": "sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q==", - "dev": true + "dev": true, + "engines": { + "node": ">=4", + "yarn": "*" + } }, - "urix": { + "node_modules/urix": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "deprecated": "Please see https://github.com/lydell/urix#deprecated", "dev": true }, - "use": { + "node_modules/use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "useragent": { + "node_modules/useragent": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/useragent/-/useragent-2.3.0.tgz", "integrity": "sha512-4AoH4pxuSvHCjqLO04sU6U/uE65BYza8l/KKBS0b0hnUPWi+cQ2BpeTEwejCSx9SPV5/U03nniDTrWx5NrmKdw==", "dev": true, - "requires": { + "dependencies": { "lru-cache": "4.1.x", "tmp": "0.0.x" } }, - "util-deprecate": { + "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, - "utils-merge": { + "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", - "dev": true + "dev": true, + "engines": { + "node": ">= 0.4.0" + } }, - "void-elements": { + "node_modules/void-elements": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "which": { + "node_modules/which": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", "dev": true, - "requires": { + "dependencies": { "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" } }, - "wordwrap": { + "node_modules/wordwrap": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.4.0" + } }, - "wrappy": { + "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, - "ws": { + "node_modules/ws": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", "dev": true, - "requires": { + "dependencies": { "async-limiter": "~1.0.0", "safe-buffer": "~5.1.0", "ultron": "~1.1.0" } }, - "xmlbuilder": { + "node_modules/xmlbuilder": { "version": "8.2.2", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-8.2.2.tgz", "integrity": "sha1-aSSGc0ELS6QuGmE2VR0pIjNap3M=", - "dev": true + "dev": true, + "engines": { + "node": ">=4.0" + } }, - "xmlhttprequest-ssl": { + "node_modules/xmlhttprequest-ssl": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.4.0" + } }, - "yallist": { + "node_modules/yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", "dev": true }, - "yeast": { + "node_modules/yeast": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=", From 2596f2541159853ea4ba7b238e4d4d24f2b13a8e Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Mon, 6 Mar 2023 16:09:53 +0200 Subject: [PATCH 10/50] chore: openapi-schema-validator dev dependency --- package-lock.json | 120 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 121 insertions(+) diff --git a/package-lock.json b/package-lock.json index 0cbce24f..853c0ce9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,6 +6,7 @@ "": { "name": "reitit", "devDependencies": { + "@seriousme/openapi-schema-validator": "^2.1.0", "karma": "^4.1.0", "karma-chrome-launcher": "^2.2.0", "karma-cli": "^2.0.0", @@ -13,6 +14,21 @@ "karma-junit-reporter": "^1.2.0" } }, + "node_modules/@seriousme/openapi-schema-validator": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@seriousme/openapi-schema-validator/-/openapi-schema-validator-2.1.0.tgz", + "integrity": "sha512-J461zq7Qj4N/SQlUiyXFelGqtKJenW9DnTjX5fraLk9Lmybq7B6goBieAlMf3D2W+grrVz/hSDodB0faoD9y2Q==", + "dev": true, + "dependencies": { + "ajv": "^8.11.0", + "ajv-draft-04": "^1.0.0", + "ajv-formats": "^2.1.1", + "js-yaml": "^4.1.0" + }, + "bin": { + "validate-api": "bin/validate-api-cli.js" + } + }, "node_modules/accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", @@ -32,6 +48,53 @@ "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=", "dev": true }, + "node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-draft-04": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", + "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", + "dev": true, + "peerDependencies": { + "ajv": "^8.5.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, "node_modules/anymatch": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", @@ -54,6 +117,12 @@ "node": ">=0.10.0" } }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, "node_modules/arr-diff": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", @@ -820,6 +889,12 @@ "node": ">=0.10.0" } }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, "node_modules/fill-range": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", @@ -1985,6 +2060,24 @@ "node": ">=0.10.0" } }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, "node_modules/jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", @@ -2665,6 +2758,15 @@ "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", "dev": true }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/qjobs": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", @@ -2798,6 +2900,15 @@ "node": ">=0.10" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -3510,6 +3621,15 @@ "yarn": "*" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/urix": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", diff --git a/package.json b/package.json index d426ec53..aa7c5d4a 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "name": "reitit", "private": true, "devDependencies": { + "@seriousme/openapi-schema-validator": "^2.1.0", "karma": "^4.1.0", "karma-chrome-launcher": "^2.2.0", "karma-cli": "^2.0.0", From 8e099febdd0a3f0cbef932d73b11ccb5bb51cc2e Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Mon, 6 Mar 2023 15:14:49 +0200 Subject: [PATCH 11/50] test: validate generated openapi spec --- test/cljc/reitit/openapi_test.clj | 58 ++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 16 deletions(-) diff --git a/test/cljc/reitit/openapi_test.clj b/test/cljc/reitit/openapi_test.clj index 0f999a01..2b3ad7d2 100644 --- a/test/cljc/reitit/openapi_test.clj +++ b/test/cljc/reitit/openapi_test.clj @@ -1,5 +1,6 @@ (ns reitit.openapi-test - (:require [clojure.test :refer [deftest is testing]] + (:require [clojure.java.shell :as shell] + [clojure.test :refer [deftest is testing]] [jsonista.core :as j] [matcher-combinators.test :refer [match?]] [matcher-combinators.matchers :as matchers] @@ -14,6 +15,16 @@ [schema.core :as s] [spec-tools.data-spec :as ds])) +(defn validate + "Returns nil if data is a valid openapi spec, otherwise validation result" + [data] + (let [file (java.io.File/createTempFile "reitit-openapi" ".json")] + (.deleteOnExit file) + (spit file (j/write-value-as-string data)) + (let [result (shell/sh "npx" "-p" "@seriousme/openapi-schema-validator" "validate-api" (.getPath file))] + (when-not (zero? (:exit result)) + (j/read-value (:out result)))))) + (def app (ring/ring-handler (ring/router @@ -22,7 +33,8 @@ ["/openapi.json" {:get {:no-doc true - :openapi {:info {:title "my-api"}} + :openapi {:info {:title "my-api" + :version "0.0.1"}} :handler (openapi/create-openapi-handler)}}] ["/spec" {:coercion spec/coercion} @@ -32,7 +44,8 @@ :path {:z int?}} :openapi {:responses {400 {:description "kosh" :content {"application/json" {:schema {:type "string"}}}}}} - :responses {200 {:body {:total int?}} + :responses {200 {:description "success" + :body {:total int?}} 500 {:description "fail"}} :handler (fn [{{{:keys [x y]} :query {:keys [z]} :path} :parameters}] @@ -42,7 +55,8 @@ :path {:z int?}} :openapi {:responses {400 {:content {"application/json" {:schema {:type "string"}}} :description "kosh"}}} - :responses {200 {:body {:total int?}} + :responses {200 {:description "success" + :body {:total int?}} 500 {:description "fail"}} :handler (fn [{{{:keys [z]} :path xs :body} :parameters}] @@ -55,7 +69,8 @@ :path [:map [:z int?]]} :openapi {:responses {400 {:description "kosh" :content {"application/json" {:schema {:type "string"}}}}}} - :responses {200 {:body [:map [:total int?]]} + :responses {200 {:description "success" + :body [:map [:total int?]]} 500 {:description "fail"}} :handler (fn [{{{:keys [x y]} :query {:keys [z]} :path} :parameters}] @@ -65,7 +80,8 @@ :path [:map [:z int?]]} :openapi {:responses {400 {:description "kosh" :content {"application/json" {:schema {:type "string"}}}}}} - :responses {200 {:body [:map [:total int?]]} + :responses {200 {:description "success" + :body [:map [:total int?]]} 500 {:description "fail"}} :handler (fn [{{{:keys [z]} :path xs :body} :parameters}] @@ -78,7 +94,8 @@ :path {:z s/Int}} :openapi {:responses {400 {:content {"application/json" {:schema {:type "string"}}} :description "kosh"}}} - :responses {200 {:body {:total s/Int}} + :responses {200 {:description "success" + :body {:total s/Int}} 500 {:description "fail"}} :handler (fn [{{{:keys [x y]} :query {:keys [z]} :path} :parameters}] @@ -88,7 +105,8 @@ :path {:z s/Int}} :openapi {:responses {400 {:content {"application/json" {:schema {:type "string"}}} :description "kosh"}}} - :responses {200 {:body {:total s/Int}} + :responses {200 {:description "success" + :body {:total s/Int}} 500 {:description "fail"}} :handler (fn [{{{:keys [z]} :path xs :body} :parameters}] @@ -115,7 +133,8 @@ :uri "/api/openapi.json"})) expected {:x-id #{::math} :openapi "3.1.0" - :info {:title "my-api"} + :info {:title "my-api" + :version "0.0.1"} :paths {"/api/spec/plus/{z}" {:get {:parameters [{:in "query" :name "x" :description "" @@ -134,7 +153,8 @@ :required true :schema {:type "integer" :format "int64"}}] - :responses {200 {:content {"application/json" {:schema {:type "object" + :responses {200 {:description "success" + :content {"application/json" {:schema {:type "object" :properties {"total" {:format "int64" :type "integer"}} :required ["total"]}}}} @@ -152,7 +172,8 @@ :format "int64"} :type "array"} {:type "null"}]}}}} - :responses {200 {:content {"application/json" {:schema {:properties {"total" {:format "int64" + :responses {200 {:description "success" + :content {"application/json" {:schema {:properties {"total" {:format "int64" :type "integer"}} :required ["total"] :type "object"}}}} @@ -172,7 +193,8 @@ :name :z :required true :schema {:type "integer"}}] - :responses {200 {:content {"application/json" {:schema {:type "object" + :responses {200 {:description "success" + :content {"application/json" {:schema {:type "object" :properties {:total {:type "integer"}} :additionalProperties false :required [:total]}}}} @@ -187,7 +209,8 @@ :requestBody {:content {"application/json" {:schema {:oneOf [{:items {:type "integer"} :type "array"} {:type "null"}]}}}} - :responses {200 {:content {"application/json" {:schema {:properties {:total {:type "integer"}} + :responses {200 {:description "success" + :content {"application/json" {:schema {:properties {:total {:type "integer"}} :required [:total] :additionalProperties false :type "object"}}}} @@ -213,7 +236,8 @@ :required true :schema {:type "integer" :format "int32"}}] - :responses {200 {:content {"application/json" {:schema {:additionalProperties false + :responses {200 {:description "success" + :content {"application/json" {:schema {:additionalProperties false :properties {"total" {:format "int32" :type "integer"}} :required ["total"] @@ -232,7 +256,8 @@ :items {:type "integer" :format "int32"}} {:type "null"}]}}}} - :responses {200 {:content {"application/json" {:schema {:properties {"total" {:format "int32" + :responses {200 {:description "success" + :content {"application/json" {:schema {:properties {"total" {:format "int32" :type "integer"}} :additionalProperties false :required ["total"] @@ -241,7 +266,8 @@ :content {"application/json" {:schema {:type "string"}}}} 500 {:description "fail"}} :summary "plus with body"}}}}] - (is (= expected spec))))) + (is (= expected spec)) + (is (nil? (validate spec)))))) (defn spec-paths [app uri] (-> {:request-method :get, :uri uri} app :body :paths keys)) From 11534551da0ba7853163cf4f2c11679ac2b25bc4 Mon Sep 17 00:00:00 2001 From: dgb23 Date: Tue, 7 Mar 2023 00:13:15 +0100 Subject: [PATCH 12/50] Update composing_routers.md I wondered how one would wrap a ring handler that can be recreated at runtime without restarting the server. @ikitommi suggested to use the recently added `reloading-ring-handler` as a starting point. I propose a small example here that illustrates the pattern. --- doc/advanced/composing_routers.md | 46 ++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/doc/advanced/composing_routers.md b/doc/advanced/composing_routers.md index 4a0f37c9..87a14ea0 100644 --- a/doc/advanced/composing_routers.md +++ b/doc/advanced/composing_routers.md @@ -405,9 +405,53 @@ All the beer-routes now match in constant time. |-----------------|---------|----------------------- | `/beers/sahti` | 40ns | static +### Wrapping a swappable ring handler + +In order for a ring handler to be recomposed, we can wrap it into a handler that dereferences it on request. + +```clj +(defn deref-handler [rf] + (fn + ([request] (@rf request)) + ([request respond raise] (@rf request respond raise)))) +``` + +A simplified beer router version that creates a ring-handler. + +```clj +(defn create-ring-handler [beers] + (ring/ring-handler + (ring/router + [["/beers" + (when (seq beers) + (for [beer beers] + [(str "/" beer) + {:get (fn [_] {:status 200 :body beer})}]))]]))) + +(def ring-handler + (atom (create-ring-handler nil))) + +(defn reset-router! [beers] + (reset! ring-handler (create-ring-handler beers))) +``` + +We don't have any matching routes yet. + +```clj +((deref-handler ring-handler) {:request-method :get :uri "/beers/lager"}) +; nil +``` + +But we can add them later. + +```clj +(reset-router! ["lager"]) +((deref-handler ring-handler) {:request-method :get :uri "/beers/lager"}) +; {:status 200, :body "lager"} +``` + ## TODO -* add an example how to do dynamic routing with `reitit-ring` * maybe create a `recursive-router` into a separate ns with all `Router` functions implemented correctly? maybe not... * add `reitit.core/merge-routes` to effectively merge routes with route data From 16145dbdce29c461da9e87e3903f73192cb1b945 Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Tue, 7 Mar 2023 09:10:37 +0200 Subject: [PATCH 13/50] test: validate generated openapi specs in all tests --- test/cljc/reitit/openapi_test.clj | 33 ++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/test/cljc/reitit/openapi_test.clj b/test/cljc/reitit/openapi_test.clj index 2b3ad7d2..d43acb64 100644 --- a/test/cljc/reitit/openapi_test.clj +++ b/test/cljc/reitit/openapi_test.clj @@ -356,16 +356,19 @@ (let [app (ring/ring-handler (ring/router [["/parameters" - {:post {:coercion @coercion + {:post {:decription "parameters" + :coercion @coercion :parameters {:query (->schema :q) :body (->schema :b) :header (->schema :h) :cookie (->schema :c) :path (->schema :p)} - :responses {200 {:body (->schema :ok)}} + :responses {200 {:description "success" + :body (->schema :ok)}} :handler identity}}] ["/openapi.json" {:get {:handler (openapi/create-openapi-handler) + :openapi {:info {:title "" :version "0.0.1"}} :no-doc true}}]])) spec (-> {:request-method :get :uri "/openapi.json"} @@ -406,7 +409,9 @@ :required ["ok"]}} (-> spec (get-in [:paths "/parameters" :post :responses 200 :content "application/json"]) - normalize)))))))) + normalize)))) + (testing "spec is valid" + (is (nil? (validate spec)))))))) (deftest per-content-type-test (doseq [[coercion ->schema] @@ -417,16 +422,19 @@ (let [app (ring/ring-handler (ring/router [["/parameters" - {:post {:coercion @coercion + {:post {:description "parameters" + :coercion @coercion :parameters {:request {:content {"application/json" (->schema :b) "application/edn" (->schema :c)}}} - :responses {200 {:content {"application/json" (->schema :ok) + :responses {200 {:description "success" + :content {"application/json" (->schema :ok) "application/edn" (->schema :edn)}}} :handler (fn [req] {:status 200 :body (-> req :parameters :request)})}}] ["/openapi.json" {:get {:handler (openapi/create-openapi-handler) + :openapi {:info {:title "" :version "0.0.1"}} :no-doc true}}]] {:data {:middleware [rrc/coerce-request-middleware rrc/coerce-response-middleware]}})) @@ -483,7 +491,9 @@ (try (app (assoc query :body-params {:z 1})) (catch clojure.lang.ExceptionInfo e - (select-keys (ex-data e) [:type :in])))))))))))) + (select-keys (ex-data e) [:type :in])))))))) + (testing "spec is valid" + (is (nil? (validate spec)))))))) (deftest default-content-type-test (doseq [[coercion ->schema] @@ -496,17 +506,20 @@ (let [app (ring/ring-handler (ring/router [["/parameters" - {:post {:coercion @coercion + {:post {:description "parameters" + :coercion @coercion :content-types [content-type] ;; TODO should this be under :openapi ? :parameters {:request {:content {"application/transit" (->schema :transit)} :body (->schema :default)}} - :responses {200 {:content {"application/transit" (->schema :transit)} + :responses {200 {:description "success" + :content {"application/transit" (->schema :transit)} :body (->schema :default)}} :handler (fn [req] {:status 200 :body (-> req :parameters :request)})}}] ["/openapi.json" {:get {:handler (openapi/create-openapi-handler) + :openapi {:info {:title "" :version "0.0.1"}} :no-doc true}}]] {:data {:middleware [rrc/coerce-request-middleware rrc/coerce-response-middleware]}})) @@ -523,4 +536,6 @@ (is (match? (matchers/in-any-order [content-type "application/transit"]) (-> spec (get-in [:paths "/parameters" :post :responses 200 :content]) - keys)))))))))) + keys)))) + (testing "spec is valid" + (is (nil? (validate spec)))))))))) From 2cc6e33654030a1bf366ad66c0453891a4be1a08 Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Wed, 8 Mar 2023 10:12:39 +0200 Subject: [PATCH 14/50] test: openapi operationId, tags and deprecated --- test/cljc/reitit/openapi_test.clj | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/test/cljc/reitit/openapi_test.clj b/test/cljc/reitit/openapi_test.clj index d43acb64..8f950c89 100644 --- a/test/cljc/reitit/openapi_test.clj +++ b/test/cljc/reitit/openapi_test.clj @@ -40,9 +40,12 @@ ["/spec" {:coercion spec/coercion} ["/plus/:z" {:get {:summary "plus" + :tags [:plus :spec] :parameters {:query {:x int?, :y int?} :path {:z int?}} - :openapi {:responses {400 {:description "kosh" + :openapi {:operationId "spec-plus" + :deprecated true + :responses {400 {:description "kosh" :content {"application/json" {:schema {:type "string"}}}}}} :responses {200 {:description "success" :body {:total int?}} @@ -65,6 +68,7 @@ ["/malli" {:coercion malli/coercion} ["/plus/*z" {:get {:summary "plus" + :tags [:plus :malli] :parameters {:query [:map [:x int?] [:y int?]] :path [:map [:z int?]]} :openapi {:responses {400 {:description "kosh" @@ -90,6 +94,7 @@ ["/schema" {:coercion schema/coercion} ["/plus/*z" {:get {:summary "plus" + :tags [:plus :schema] :parameters {:query {:x s/Int, :y s/Int} :path {:z s/Int}} :openapi {:responses {400 {:content {"application/json" {:schema {:type "string"}}} @@ -161,6 +166,9 @@ 400 {:description "kosh" :content {"application/json" {:schema {:type "string"}}}} 500 {:description "fail"}} + :operationId "spec-plus" + :deprecated true + :tags [:plus :spec] :summary "plus"} :post {:parameters [{:in "path" :name "z" @@ -201,6 +209,7 @@ 400 {:description "kosh" :content {"application/json" {:schema {:type "string"}}}} 500 {:description "fail"}} + :tags [:plus :malli] :summary "plus"} :post {:parameters [{:in "path" :name :z @@ -245,6 +254,7 @@ 400 {:description "kosh" :content {"application/json" {:schema {:type "string"}}}} 500 {:description "fail"}} + :tags [:plus :schema] :summary "plus"} :post {:parameters [{:in "path" :name "z" From 52b7402575bfd82825217ce5d8827406c81c1ec3 Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Tue, 7 Mar 2023 10:34:28 +0200 Subject: [PATCH 15/50] doc: openapi in examples/http-swagger --- examples/http-swagger/README.md | 8 ++++++-- examples/http-swagger/src/example/server.clj | 9 +++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/examples/http-swagger/README.md b/examples/http-swagger/README.md index 651e9bab..e5df1cce 100644 --- a/examples/http-swagger/README.md +++ b/examples/http-swagger/README.md @@ -1,4 +1,4 @@ -# Http with Swagger example +# Http with Swagger/OpenAPI example ## Usage @@ -7,6 +7,10 @@ (start) ``` +- Swagger spec served at +- Openapi spec served at +- Swagger UI served at + To test the endpoints using [httpie](https://httpie.org/): ```bash @@ -20,4 +24,4 @@ http GET :3000/async results==1 seed==reitit ## License -Copyright © 2018 Metosin Oy +Copyright © 2018-2023 Metosin Oy diff --git a/examples/http-swagger/src/example/server.clj b/examples/http-swagger/src/example/server.clj index 7bb7f5d6..71aef569 100644 --- a/examples/http-swagger/src/example/server.clj +++ b/examples/http-swagger/src/example/server.clj @@ -4,6 +4,7 @@ [reitit.coercion.spec] [reitit.swagger :as swagger] [reitit.swagger-ui :as swagger-ui] + [reitit.openapi :as openapi] [reitit.http.coercion :as coercion] [reitit.dev.pretty :as pretty] [reitit.interceptor.sieppari :as sieppari] @@ -44,6 +45,14 @@ :swagger {:info {:title "my-api" :description "with reitit-http"}} :handler (swagger/create-swagger-handler)}}] + ["/openapi.json" + {:get {:no-doc true + ;; TODO swagger-ui hasn't released support for OAS 3.1 yet, so we pretend it's 3.0 + :openapi {:openapi "3.0.0" + :info {:title "my-api" + :description "with reitit-http" + :version "0.0.1"}} + :handler (openapi/create-openapi-handler)}}] ["/files" {:swagger {:tags ["files"]}} From 8df8bf06cc109ef94dfddf33554c7fe1833d0180 Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Wed, 8 Mar 2023 09:32:23 +0200 Subject: [PATCH 16/50] doc: initial docs for openapi support & per-content-type coercion --- doc/SUMMARY.md | 1 + doc/cljdoc.edn | 1 + doc/ring/coercion.md | 39 ++++++++++++++++++++----- doc/ring/content_negotiation.md | 2 ++ doc/ring/openapi.md | 51 +++++++++++++++++++++++++++++++++ doc/ring/swagger.md | 2 ++ 6 files changed, 89 insertions(+), 7 deletions(-) create mode 100644 doc/ring/openapi.md diff --git a/doc/SUMMARY.md b/doc/SUMMARY.md index d70d65e4..f0f2cce9 100644 --- a/doc/SUMMARY.md +++ b/doc/SUMMARY.md @@ -41,6 +41,7 @@ * [Route Data Validation](ring/route_data_validation.md) * [Compiling Middleware](ring/compiling_middleware.md) * [Swagger Support](ring/swagger.md) +* [OpenAPI Support](ring/openapi.md) * [RESTful form methods](ring/RESTful_form_methods.md) ## HTTP diff --git a/doc/cljdoc.edn b/doc/cljdoc.edn index 3ec69060..b03b0bd3 100644 --- a/doc/cljdoc.edn +++ b/doc/cljdoc.edn @@ -47,6 +47,7 @@ ["Route Data Validation" {:file "doc/ring/route_data_validation.md"}] ["Compiling Middleware" {:file "doc/ring/compiling_middleware.md"}] ["Swagger Support" {:file "doc/ring/swagger.md"}] + ["OpenAPI Support" {:file "doc/ring/openapi.md"}] ["RESTful form methods" {:file "doc/ring/RESTful_form_methods.md"}]] ["HTTP" {} ["Interceptors" {:file "doc/http/interceptors.md"}] diff --git a/doc/ring/coercion.md b/doc/ring/coercion.md index faed3a8b..930c10df 100644 --- a/doc/ring/coercion.md +++ b/doc/ring/coercion.md @@ -4,13 +4,14 @@ Basic coercion is explained in detail [in the Coercion Guide](../coercion/coerci The following request parameters are currently supported: -| type | request source | -|-----------|------------------| -| `:query` | `:query-params` | -| `:body` | `:body-params` | -| `:form` | `:form-params` | -| `:header` | `:header-params` | -| `:path` | `:path-params` | +| type | request source | +|------------|--------------------------------------------------| +| `:query` | `:query-params` | +| `:body` | `:body-params` | +| `:request` | `:body-params`, allows per-content-type coercion | +| `:form` | `:form-params` | +| `:header` | `:header-params` | +| `:path` | `:path-params` | To enable coercion, the following things need to be done: @@ -148,6 +149,30 @@ Invalid response: ; :in [:response :body]}} ``` +## Per-content-type coercion + +You can also specify request and response body schemas per content-type. The syntax for this is: + +```clj +(def app + (ring/ring-handler + (ring/router + ["/api" + ["/example" {:post {:coercion reitit.coercion.schema/coercion + :parameters {:request {:content {"application/json" {:y s/Int} + "application/edn" {:z s/Int}} + ;; default if no content-type matches: + :body {:yy s/Int}}} + :responses {200 {:content {"application/json" {:w s/Int} + "application/edn" {:x s/Int}} + ;; default if no content-type matches: + :body {:ww s/Int}} + :handler ...}}]] + {:data {:middleware [rrc/coerce-exceptions-middleware + rrc/coerce-request-middleware + rrc/coerce-response-middleware]}}))) +``` + ## Pretty printing spec errors Spec problems are exposed as is in request & response coercion errors. Pretty-printers like [expound](https://github.com/bhb/expound) can be enabled like this: diff --git a/doc/ring/content_negotiation.md b/doc/ring/content_negotiation.md index fb696c10..990e642d 100644 --- a/doc/ring/content_negotiation.md +++ b/doc/ring/content_negotiation.md @@ -84,6 +84,8 @@ Server: Jetty(9.2.21.v20170120) kukka ``` +You can also specify request and response schemas per content-type. See [Coercion](coercion.md) and [OpenAPI Support](openapi.md). + ## Changing default parameters diff --git a/doc/ring/openapi.md b/doc/ring/openapi.md new file mode 100644 index 00000000..ee6ffd72 --- /dev/null +++ b/doc/ring/openapi.md @@ -0,0 +1,51 @@ +# OpenAPI Support + +Reitit can generate [OpenAPI 3.1.0](https://spec.openapis.org/oas/v3.1.0) +documentation. The feature works similarly to [Swagger documentation](swagger.md). + +The [http-swagger example](../../examples/http-swagger) also has OpenAPI documentation. + +## OpenAPI data + +The following route data keys contribute to the generated swagger specification: + +| key | description | +| ---------------|-------------| +| :openapi | map of any openapi data. Can contain keys like `:deprecated`. +| :content-types | vector of supported content types. Defaults to `["application/json"]` +| :no-doc | optional boolean to exclude endpoint from api docs +| :tags | optional set of string or keyword tags for an endpoint api docs +| :summary | optional short string summary of an endpoint +| :description | optional long description of an endpoint. Supports http://spec.commonmark.org/ + +Coercion keys also contribute to the docs: + +| key | description | +| --------------|-------------| +| :parameters | optional input parameters for a route, in a format defined by the coercion +| :responses | optional descriptions of responses, in a format defined by coercion + +Use `:request` parameter coercion (instead of `:body`) to unlock per-content-type coercions. See [Coercion](coercion.md). + +## Swagger spec + +Serving the OpenAPI specification is handled by `reitit.openapi/create-openapi-handler`. It takes no arguments and returns a ring handler which collects at request-time data from all routes and returns an OpenAPI specification as Clojure data, to be encoded by a response formatter. + +You can use the `:openapi` route data key of the `create-openapi-handler` route to populate the top level of the OpenAPI spec. + +Example: + +``` +["/openapi.json" + {:get {:handler (openapi/create-openapi-handler) + :openapi {:info {:title "my nice api" :version "0.0.1"}} + :no-doc true}}] +``` + +If you need to post-process the generated spec, just wrap the handler with a custom `Middleware` or an `Interceptor`. + +## Swagger-ui + +[Swagger-ui](https://github.com/swagger-api/swagger-ui) is a user interface to visualize and interact with the Swagger specification. To make things easy, there is a pre-integrated version of the swagger-ui as a separate module. + +- TIP: downgrade to 3.0.0 if needed diff --git a/doc/ring/swagger.md b/doc/ring/swagger.md index 5ad40b80..d576512b 100644 --- a/doc/ring/swagger.md +++ b/doc/ring/swagger.md @@ -6,6 +6,8 @@ Reitit supports [Swagger2](https://swagger.io/) documentation, thanks to [schema-tools](https://github.com/metosin/schema-tools) and [spec-tools](https://github.com/metosin/spec-tools). Documentation is extracted from route definitions, coercion `:parameters` and `:responses` and from a set of new documentation keys. +See also: [OpenAPI support](openapi.md). + To enable swagger-documentation for a Ring router: 1. annotate your routes with swagger-data From 9ae3cd0824c0e3e6e851e9bbe9060a1b3d6258f3 Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Wed, 8 Mar 2023 14:11:28 +0200 Subject: [PATCH 17/50] fix: reitit.openapi route data validation :kind set? would've worked, but there's no need to insist a set also, turn on route data validation in openapi_test.clj --- modules/reitit-openapi/src/reitit/openapi.cljc | 2 +- test/cljc/reitit/openapi_test.clj | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/modules/reitit-openapi/src/reitit/openapi.cljc b/modules/reitit-openapi/src/reitit/openapi.cljc index 6b1a465e..0328ce43 100644 --- a/modules/reitit-openapi/src/reitit/openapi.cljc +++ b/modules/reitit-openapi/src/reitit/openapi.cljc @@ -9,7 +9,7 @@ (s/def ::id (s/or :keyword keyword? :set (s/coll-of keyword? :into #{}))) (s/def ::no-doc boolean?) -(s/def ::tags (s/coll-of (s/or :keyword keyword? :string string?) :kind #{})) +(s/def ::tags (s/coll-of (s/or :keyword keyword? :string string?))) (s/def ::summary string?) (s/def ::description string?) diff --git a/test/cljc/reitit/openapi_test.clj b/test/cljc/reitit/openapi_test.clj index 8f950c89..a1c944d2 100644 --- a/test/cljc/reitit/openapi_test.clj +++ b/test/cljc/reitit/openapi_test.clj @@ -10,6 +10,7 @@ [reitit.coercion.spec :as spec] [reitit.openapi :as openapi] [reitit.ring :as ring] + [reitit.ring.spec] [reitit.ring.coercion :as rrc] [reitit.swagger-ui :as swagger-ui] [schema.core :as s] @@ -117,7 +118,8 @@ xs :body} :parameters}] {:status 200, :body {:total (+ (reduce + xs) z)}})}}]]] - {:data {:middleware [openapi/openapi-feature + {:validate reitit.ring.spec/validate + :data {:middleware [openapi/openapi-feature rrc/coerce-exceptions-middleware rrc/coerce-request-middleware rrc/coerce-response-middleware]}}))) @@ -379,7 +381,8 @@ ["/openapi.json" {:get {:handler (openapi/create-openapi-handler) :openapi {:info {:title "" :version "0.0.1"}} - :no-doc true}}]])) + :no-doc true}}]] + {:data {:middleware [openapi/openapi-feature]}})) spec (-> {:request-method :get :uri "/openapi.json"} app @@ -446,7 +449,9 @@ {:get {:handler (openapi/create-openapi-handler) :openapi {:info {:title "" :version "0.0.1"}} :no-doc true}}]] - {:data {:middleware [rrc/coerce-request-middleware + {:validate reitit.ring.spec/validate + :data {:middleware [openapi/openapi-feature + rrc/coerce-request-middleware rrc/coerce-response-middleware]}})) spec (-> {:request-method :get :uri "/openapi.json"} @@ -531,7 +536,9 @@ {:get {:handler (openapi/create-openapi-handler) :openapi {:info {:title "" :version "0.0.1"}} :no-doc true}}]] - {:data {:middleware [rrc/coerce-request-middleware + {:validate reitit.ring.spec/validate + :data {:middleware [openapi/openapi-feature + rrc/coerce-request-middleware rrc/coerce-response-middleware]}})) spec (-> {:request-method :get :uri "/openapi.json"} From 50c1af9a5b836fa1d0574b9ed47ff70e596335d5 Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Wed, 8 Mar 2023 14:20:37 +0200 Subject: [PATCH 18/50] feat: route data validation for per-content-type coercions --- modules/reitit-core/src/reitit/spec.cljc | 10 +++++++++- modules/reitit-openapi/src/reitit/openapi.cljc | 3 ++- test/cljc/reitit/ring_coercion_test.cljc | 4 +++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/modules/reitit-core/src/reitit/spec.cljc b/modules/reitit-core/src/reitit/spec.cljc index 62595b2b..d5ddaf84 100644 --- a/modules/reitit-core/src/reitit/spec.cljc +++ b/modules/reitit-core/src/reitit/spec.cljc @@ -82,14 +82,21 @@ (s/def :reitit.core.coercion/model any?) +(s/def :reitit.core.coercion/content + (s/map-of string? :reitit.core.coercion/model)) + (s/def :reitit.core.coercion/query :reitit.core.coercion/model) (s/def :reitit.core.coercion/body :reitit.core.coercion/model) +(s/def :reitit.core.coercion/request + (s/keys :opt-un [:reitit.core.coercion/content + :reitit.core.coercion/body])) (s/def :reitit.core.coercion/form :reitit.core.coercion/model) (s/def :reitit.core.coercion/header :reitit.core.coercion/model) (s/def :reitit.core.coercion/path :reitit.core.coercion/model) (s/def :reitit.core.coercion/parameters (s/keys :opt-un [:reitit.core.coercion/query :reitit.core.coercion/body + :reitit.core.coercion/request :reitit.core.coercion/form :reitit.core.coercion/header :reitit.core.coercion/path])) @@ -103,7 +110,8 @@ (s/def :reitit.core.coercion/body any?) (s/def :reitit.core.coercion/description string?) (s/def :reitit.core.coercion/response - (s/keys :opt-un [:reitit.core.coercion/body + (s/keys :opt-un [:reitit.core.coercion/content + :reitit.core.coercion/body :reitit.core.coercion/description])) (s/def :reitit.core.coercion/responses (s/map-of :reitit.core.coercion/status :reitit.core.coercion/response)) diff --git a/modules/reitit-openapi/src/reitit/openapi.cljc b/modules/reitit-openapi/src/reitit/openapi.cljc index 0328ce43..c788d2dc 100644 --- a/modules/reitit-openapi/src/reitit/openapi.cljc +++ b/modules/reitit-openapi/src/reitit/openapi.cljc @@ -12,9 +12,10 @@ (s/def ::tags (s/coll-of (s/or :keyword keyword? :string string?))) (s/def ::summary string?) (s/def ::description string?) +(s/def ::content-types (s/coll-of string?)) (s/def ::openapi (s/keys :opt-un [::id])) -(s/def ::spec (s/keys :opt-un [::openapi ::no-doc ::tags ::summary ::description])) +(s/def ::spec (s/keys :opt-un [::openapi ::no-doc ::tags ::summary ::description ::content-types])) (def openapi-feature "Feature for handling openapi-documentation for routes. diff --git a/test/cljc/reitit/ring_coercion_test.cljc b/test/cljc/reitit/ring_coercion_test.cljc index da6c804a..66d151ed 100644 --- a/test/cljc/reitit/ring_coercion_test.cljc +++ b/test/cljc/reitit/ring_coercion_test.cljc @@ -11,6 +11,7 @@ [reitit.coercion.spec :as spec] [reitit.core :as r] [reitit.ring :as ring] + [reitit.ring.spec] [reitit.ring.coercion :as rrc] [schema.core :as s] [clojure.spec.alpha] @@ -618,7 +619,8 @@ :handler (fn [req] {:status 200 :body (-> req :parameters :request)})}}]] - {:data {:middleware [rrc/coerce-request-middleware + {:validate reitit.ring.spec/validate + :data {:middleware [rrc/coerce-request-middleware rrc/coerce-response-middleware] :coercion @coercion}})) call (fn [request] From ae55b6628cb48b7a68ba3aa80b5692d2538ebe66 Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Thu, 9 Mar 2023 10:21:46 +0200 Subject: [PATCH 19/50] doc: update docstring of openapi-feature --- modules/reitit-openapi/src/reitit/openapi.cljc | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/modules/reitit-openapi/src/reitit/openapi.cljc b/modules/reitit-openapi/src/reitit/openapi.cljc index c788d2dc..fa414b63 100644 --- a/modules/reitit-openapi/src/reitit/openapi.cljc +++ b/modules/reitit-openapi/src/reitit/openapi.cljc @@ -22,16 +22,18 @@ Works both with Middleware & Interceptors. Does not participate in actual request processing, just provides specs for the new documentation keys for the route data. Should be accompanied by a - [[openapi-spec-handler]] to expose the openapi spec. + [[create-openapi-handler]] to expose the openapi spec. New route data keys contributing to openapi docs: - | key | description | - | --------------|-------------| - | :openapi | map of any openapi-data. Must have `:id` (keyword or sequence of keywords) to identify the api - | :no-doc | optional boolean to exclude endpoint from api docs - | :summary | optional short string summary of an endpoint - | :description | optional long description of an endpoint. Supports http://spec.commonmark.org/ + | key | description | + | ---------------|-------------| + | :openapi | map of any openapi-data. Can contain keys like `:deprecated`. + | :content-types | vector of supported content types. Defaults to `[\"application/json\"]` + | :no-doc | optional boolean to exclude endpoint from api docs + | :tags | optional set of string or keyword tags for an endpoint api docs + | :summary | optional short string summary of an endpoint + | :description | optional long description of an endpoint. Supports http://spec.commonmark.org/ Also the coercion keys contribute to openapi spec: @@ -40,6 +42,8 @@ | :parameters | optional input parameters for a route, in a format defined by the coercion | :responses | optional descriptions of responses, in a format defined by coercion + Use `:request` parameter coercion (instead of `:body`) to unlock per-content-type coercions. + Example: [\"/api\" From 8c87fef7b6c0591a5aa5f993c4e2f8d6a6a1122a Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Fri, 10 Mar 2023 08:49:27 +0200 Subject: [PATCH 20/50] feat: warning when swagger encounters per-content-type coercions --- modules/reitit-core/src/reitit/coercion.cljc | 26 +++++++++++++------- test/cljc/reitit/swagger_test.clj | 26 ++++++++++++++++++++ 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/modules/reitit-core/src/reitit/coercion.cljc b/modules/reitit-core/src/reitit/coercion.cljc index f6075ae9..969069ce 100644 --- a/modules/reitit-core/src/reitit/coercion.cljc +++ b/modules/reitit-core/src/reitit/coercion.cljc @@ -167,6 +167,12 @@ ;; api-docs ;; +(defn -warn-unsupported-coercions [{:keys [parameters responses] :as data}] + (when (:request parameters) + (println "WARNING [reitit.coercion]: swagger apidocs don't support :request coercion")) + (when (some :content (vals responses)) + (println "WARNING [reitit.coercion]: swagger apidocs don't support :responses :content coercion"))) + (defn get-apidocs [coercion specification data] (let [swagger-parameter {:query :query :body :body @@ -176,15 +182,17 @@ :multipart :formData}] (case specification :openapi (-get-apidocs coercion specification data) - :swagger (->> (update - data - :parameters - (fn [parameters] - (->> parameters - (map (fn [[k v]] [(swagger-parameter k) v])) - (filter first) - (into {})))) - (-get-apidocs coercion specification))))) + :swagger (do + (-warn-unsupported-coercions data) + (->> (update + data + :parameters + (fn [parameters] + (->> parameters + (map (fn [[k v]] [(swagger-parameter k) v])) + (filter first) + (into {})))) + (-get-apidocs coercion specification)))))) ;; diff --git a/test/cljc/reitit/swagger_test.clj b/test/cljc/reitit/swagger_test.clj index 0958b8b1..542b7ebd 100644 --- a/test/cljc/reitit/swagger_test.clj +++ b/test/cljc/reitit/swagger_test.clj @@ -384,3 +384,29 @@ spec (:body (app {:request-method :get, :uri "/swagger.json"}))] (is (= ["query" "body" "formData" "header" "path"] (map :in (get-in spec [:paths "/parameters" :post :parameters])))))) + +(deftest multiple-content-types-test + (testing ":request coercion" + (let [app (ring/ring-handler + (ring/router + [["/parameters" + {:post {:coercion spec/coercion + :parameters {:request {:content {"application/json" {:x string?}}}} + :handler identity}}] + ["/swagger.json" + {:get {:no-doc true + :handler (swagger/create-swagger-handler)}}]])) + output (with-out-str (app {:request-method :get, :uri "/swagger.json"}))] + (is (.contains output "WARN")))) + (testing "multiple response content types" + (let [app (ring/ring-handler + (ring/router + [["/parameters" + {:post {:coercion spec/coercion + :responses {200 {:content {"application/json" {:r string?}}}} + :handler identity}}] + ["/swagger.json" + {:get {:no-doc true + :handler (swagger/create-swagger-handler)}}]])) + output (with-out-str (app {:request-method :get, :uri "/swagger.json"}))] + (is (.contains output "WARN"))))) From 3fa50ea0f68a0c1ca4d346259fc986546f2d86ca Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Fri, 10 Mar 2023 10:17:42 +0200 Subject: [PATCH 21/50] doc: mark openapi support as alpha --- doc/ring/openapi.md | 2 ++ modules/reitit-openapi/src/reitit/openapi.cljc | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/doc/ring/openapi.md b/doc/ring/openapi.md index ee6ffd72..34536048 100644 --- a/doc/ring/openapi.md +++ b/doc/ring/openapi.md @@ -1,5 +1,7 @@ # OpenAPI Support +**Stability: alpha** + Reitit can generate [OpenAPI 3.1.0](https://spec.openapis.org/oas/v3.1.0) documentation. The feature works similarly to [Swagger documentation](swagger.md). diff --git a/modules/reitit-openapi/src/reitit/openapi.cljc b/modules/reitit-openapi/src/reitit/openapi.cljc index fa414b63..e97162d9 100644 --- a/modules/reitit-openapi/src/reitit/openapi.cljc +++ b/modules/reitit-openapi/src/reitit/openapi.cljc @@ -18,7 +18,9 @@ (s/def ::spec (s/keys :opt-un [::openapi ::no-doc ::tags ::summary ::description ::content-types])) (def openapi-feature - "Feature for handling openapi-documentation for routes. + "Stability: alpha + + Feature for handling openapi-documentation for routes. Works both with Middleware & Interceptors. Does not participate in actual request processing, just provides specs for the new documentation keys for the route data. Should be accompanied by a @@ -72,7 +74,9 @@ (-> path (trie/normalize opts) (str/replace #"\{\*" "{"))) (defn create-openapi-handler - "Create a ring handler to emit openapi spec. Collects all routes from router which have + "Stability: alpha + + Create a ring handler to emit openapi spec. Collects all routes from router which have an intersecting `[:openapi :id]` and which are not marked with `:no-doc` route data." [] (fn create-openapi From 304b77cb7dc8aff464fcc34dd929c8e50fd498bf Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Fri, 10 Mar 2023 13:53:10 +0200 Subject: [PATCH 22/50] doc: mention lack of swagger-ui support for openapi 3.1 --- doc/ring/openapi.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/ring/openapi.md b/doc/ring/openapi.md index 34536048..98ee4e71 100644 --- a/doc/ring/openapi.md +++ b/doc/ring/openapi.md @@ -29,7 +29,7 @@ Coercion keys also contribute to the docs: Use `:request` parameter coercion (instead of `:body`) to unlock per-content-type coercions. See [Coercion](coercion.md). -## Swagger spec +## OpenAPI spec Serving the OpenAPI specification is handled by `reitit.openapi/create-openapi-handler`. It takes no arguments and returns a ring handler which collects at request-time data from all routes and returns an OpenAPI specification as Clojure data, to be encoded by a response formatter. @@ -48,6 +48,6 @@ If you need to post-process the generated spec, just wrap the handler with a cus ## Swagger-ui -[Swagger-ui](https://github.com/swagger-api/swagger-ui) is a user interface to visualize and interact with the Swagger specification. To make things easy, there is a pre-integrated version of the swagger-ui as a separate module. +[Swagger-UI](https://github.com/swagger-api/swagger-ui) is a user interface to visualize and interact with the Swagger specification. To make things easy, there is a pre-integrated version of the swagger-ui as a separate module. -- TIP: downgrade to 3.0.0 if needed +As of Swagger-UI 4.17, OpenAPI 3.1 is not yet supported. However, most OpenAPI specs generated by reitit are also OpenAPI 3.0 compatible. You can overwrite the version string in the spec to 3.0.0, and you'll be able to use Swagger-UI. See [the http-swagger example](../../examples/http-swagger). From 7defd98808e0b654f90c76cc96bd4520fbe0f270 Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Fri, 10 Mar 2023 14:34:05 +0200 Subject: [PATCH 23/50] doc: Swagger-UI 5.0.0-alpha.0 has OpenAPI 3.1 support mention in docs, use in http-swagger example --- doc/ring/openapi.md | 2 +- examples/http-swagger/project.clj | 3 ++- examples/http-swagger/src/example/server.clj | 4 +--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/doc/ring/openapi.md b/doc/ring/openapi.md index 98ee4e71..3a1f5fcf 100644 --- a/doc/ring/openapi.md +++ b/doc/ring/openapi.md @@ -50,4 +50,4 @@ If you need to post-process the generated spec, just wrap the handler with a cus [Swagger-UI](https://github.com/swagger-api/swagger-ui) is a user interface to visualize and interact with the Swagger specification. To make things easy, there is a pre-integrated version of the swagger-ui as a separate module. -As of Swagger-UI 4.17, OpenAPI 3.1 is not yet supported. However, most OpenAPI specs generated by reitit are also OpenAPI 3.0 compatible. You can overwrite the version string in the spec to 3.0.0, and you'll be able to use Swagger-UI. See [the http-swagger example](../../examples/http-swagger). +Note: you need Swagger-UI 5 for OpenAPI 3.1 support. As of 2023-03-10, a v5.0.0-alpha.0 is out. diff --git a/examples/http-swagger/project.clj b/examples/http-swagger/project.clj index 6676368e..47b8c500 100644 --- a/examples/http-swagger/project.clj +++ b/examples/http-swagger/project.clj @@ -3,5 +3,6 @@ :dependencies [[org.clojure/clojure "1.10.0"] [ring/ring-jetty-adapter "1.7.1"] [aleph "0.4.7-alpha5"] - [metosin/reitit "0.6.0"]] + [metosin/reitit "0.6.0"] + [metosin/ring-swagger-ui "5.0.0-alpha.0"]] :repl-options {:init-ns example.server}) diff --git a/examples/http-swagger/src/example/server.clj b/examples/http-swagger/src/example/server.clj index 71aef569..711dc270 100644 --- a/examples/http-swagger/src/example/server.clj +++ b/examples/http-swagger/src/example/server.clj @@ -47,9 +47,7 @@ :handler (swagger/create-swagger-handler)}}] ["/openapi.json" {:get {:no-doc true - ;; TODO swagger-ui hasn't released support for OAS 3.1 yet, so we pretend it's 3.0 - :openapi {:openapi "3.0.0" - :info {:title "my-api" + :openapi {:info {:title "my-api" :description "with reitit-http" :version "0.0.1"}} :handler (openapi/create-openapi-handler)}}] From 20cafa3d9b0f500b3ec7ffacccad0514a6ec8756 Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Mon, 13 Mar 2023 07:33:42 +0200 Subject: [PATCH 24/50] doc: examples/http-swagger: tags for openapi, openapi-feature --- examples/http-swagger/src/example/server.clj | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/http-swagger/src/example/server.clj b/examples/http-swagger/src/example/server.clj index 711dc270..a2e09720 100644 --- a/examples/http-swagger/src/example/server.clj +++ b/examples/http-swagger/src/example/server.clj @@ -53,7 +53,7 @@ :handler (openapi/create-openapi-handler)}}] ["/files" - {:swagger {:tags ["files"]}} + {:tags ["files"]} ["/upload" {:post {:summary "upload a file" @@ -74,7 +74,7 @@ (io/resource "reitit.png"))})}}]] ["/async" - {:get {:swagger {:tags ["async"]} + {:get {:tags ["async"] :summary "fetches random users asynchronously over the internet" :parameters {:query (s/keys :req-un [::results] :opt-un [::seed])} :responses {200 {:body any?}} @@ -91,7 +91,7 @@ :body results})))}}] ["/math" - {:swagger {:tags ["math"]}} + {:tags ["math"]} ["/plus" {:get {:summary "plus with data-spec query parameters" @@ -129,6 +129,8 @@ :muuntaja m/instance :interceptors [;; swagger feature swagger/swagger-feature + ;; openapi feature + openapi/openapi-feature ;; query-params & form-params (parameters/parameters-interceptor) ;; content-negotiation From 9b50baca0cfc3315c9551f50b94ae31ea48fa0d6 Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Mon, 13 Mar 2023 15:37:49 +0200 Subject: [PATCH 25/50] doc: examples/http-swagger: authentication --- examples/http-swagger/src/example/server.clj | 29 ++++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/examples/http-swagger/src/example/server.clj b/examples/http-swagger/src/example/server.clj index a2e09720..b5e1fc58 100644 --- a/examples/http-swagger/src/example/server.clj +++ b/examples/http-swagger/src/example/server.clj @@ -43,13 +43,21 @@ [["/swagger.json" {:get {:no-doc true :swagger {:info {:title "my-api" - :description "with reitit-http"}} + :description "with reitit-http"} + ;; used in /secure APIs below + :securityDefinitions {"auth" {:type :apiKey + :in :header + :name "Example-Api-Key"}}} :handler (swagger/create-swagger-handler)}}] ["/openapi.json" {:get {:no-doc true :openapi {:info {:title "my-api" :description "with reitit-http" - :version "0.0.1"}} + :version "0.0.1"} + ;; used in /secure APIs below + :components {:securitySchemes {"auth" {:type :apiKey + :in :header + :name "Example-Api-Key"}}}} :handler (openapi/create-openapi-handler)}}] ["/files" @@ -119,7 +127,22 @@ :responses {200 {:body (s/keys :req-un [::total])}} :handler (fn [{{{:keys [x y]} :body} :parameters}] {:status 200 - :body {:total (- x y)}})}}]]] + :body {:total (- x y)}})}}]] + ["/secure" + {:tags ["secure"] + :openapi {:security [{"auth" []}]} + :swagger {:security [{"auth" []}]}} + ["/get" + {:get {:summary "endpoint authenticated with a header" + :responses {200 {:body {:secret string?}} + 401 {:body {:error string?}}} + :handler (fn [request] + ;; In a real app authentication would be handled by middleware + (if (= "secret" (get-in request [:headers "example-api-key"])) + {:status 200 + :body {:secret "I am a marmot"}} + {:status 401 + :body {:error "unauthorized"}}))}}]]] {;:reitit.interceptor/transform dev/print-context-diffs ;; pretty context diffs ;;:validate spec/validate ;; enable spec validation for route data From 11f47527f10a7a8abe27c16de9a537a209b73b23 Mon Sep 17 00:00:00 2001 From: Tommi Reiman Date: Tue, 14 Mar 2023 12:21:20 +0200 Subject: [PATCH 26/50] add reitit-openapi module, 0.5.18 -> 0.6.0 --- modules/reitit-openapi/project.clj | 2 +- modules/reitit/project.clj | 1 + project.clj | 1 + scripts/lein-modules | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/reitit-openapi/project.clj b/modules/reitit-openapi/project.clj index 535b51ec..1fa0b322 100644 --- a/modules/reitit-openapi/project.clj +++ b/modules/reitit-openapi/project.clj @@ -1,4 +1,4 @@ -(defproject metosin/reitit-openapi "0.5.18" +(defproject metosin/reitit-openapi "0.6.0" :description "Reitit: OpenAPI-support" :url "https://github.com/metosin/reitit" :license {:name "Eclipse Public License" diff --git a/modules/reitit/project.clj b/modules/reitit/project.clj index e587547f..41ec3d9a 100644 --- a/modules/reitit/project.clj +++ b/modules/reitit/project.clj @@ -19,6 +19,7 @@ [metosin/reitit-http] [metosin/reitit-interceptors] [metosin/reitit-swagger] + [metosin/reitit-openapi] [metosin/reitit-swagger-ui] [metosin/reitit-frontend] [metosin/reitit-sieppari] diff --git a/project.clj b/project.clj index aaf41690..72936964 100644 --- a/project.clj +++ b/project.clj @@ -26,6 +26,7 @@ [metosin/reitit-http "0.6.0"] [metosin/reitit-interceptors "0.6.0"] [metosin/reitit-swagger "0.6.0"] + [metosin/reitit-openapi "0.6.0"] [metosin/reitit-swagger-ui "0.6.0"] [metosin/reitit-frontend "0.6.0"] [metosin/reitit-sieppari "0.6.0"] diff --git a/scripts/lein-modules b/scripts/lein-modules index 24a87858..59ec5747 100755 --- a/scripts/lein-modules +++ b/scripts/lein-modules @@ -14,6 +14,7 @@ for ext in \ reitit-http \ reitit-interceptors \ reitit-swagger \ + reitit-openapi \ reitit-swagger-ui \ reitit-frontend \ reitit-sieppari \ From bbaf4b27f7b5a98288efaceef23e19d6de43f305 Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Tue, 14 Mar 2023 14:37:10 +0200 Subject: [PATCH 27/50] refactor: remove commented-out code --- modules/reitit-openapi/src/reitit/openapi.cljc | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules/reitit-openapi/src/reitit/openapi.cljc b/modules/reitit-openapi/src/reitit/openapi.cljc index e97162d9..5402c75c 100644 --- a/modules/reitit-openapi/src/reitit/openapi.cljc +++ b/modules/reitit-openapi/src/reitit/openapi.cljc @@ -90,14 +90,12 @@ :x-id ids})) accept-route (fn [route] (-> route second :openapi :id (or ::default) (trie/into-set) (set/intersection ids) seq)) - ;base-openapi-spec {:responses ^:displace {:default {:description ""}}} transform-endpoint (fn [[method {{:keys [coercion no-doc openapi] :as data} :data middleware :middleware interceptors :interceptors}]] (if (and data (not no-doc)) [method (meta-merge - #_base-openapi-spec (apply meta-merge (keep (comp :openapi :data) middleware)) (apply meta-merge (keep (comp :openapi :data) interceptors)) (if coercion From 814c8b88e2f151d10be4781bfba5b40021e9fe07 Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Tue, 14 Mar 2023 14:39:05 +0200 Subject: [PATCH 28/50] refactor: factor out -identity-coercer --- modules/reitit-core/src/reitit/coercion.cljc | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/reitit-core/src/reitit/coercion.cljc b/modules/reitit-core/src/reitit/coercion.cljc index 969069ce..2c1af0c7 100644 --- a/modules/reitit-core/src/reitit/coercion.cljc +++ b/modules/reitit-core/src/reitit/coercion.cljc @@ -79,6 +79,9 @@ (defn extract-request-format-default [request] (-> request :muuntaja/request :format)) +(defn -identity-coercer [value _format] + value) + ;; TODO: support faster key walking, walk/keywordize-keys is quite slow... (defn request-coercer [coercion type model {::keys [extract-request-format parameter-coercion serialize-failed-result] :or {extract-request-format extract-request-format-default @@ -102,7 +105,7 @@ format (extract-request-format request) coercer (or (format->coercer format) (format->coercer :default) - (fn [value _format] value)) + -identity-coercer) result (coercer value format)] (if (error? result) (request-coercion-failed! result coercion value in request serialize-failed-result) @@ -124,7 +127,7 @@ (fn [request response] (let [format (extract-response-format request response) value (:body response) - coercer (get per-format-coercers format (or default (fn [value _format] value))) + coercer (get per-format-coercers format (or default -identity-coercer)) result (coercer value format)] (if (error? result) (response-coercion-failed! result coercion value request response serialize-failed-result) From 25b75c877a99e1d38f8ebe1fbf7828125bf62f6d Mon Sep 17 00:00:00 2001 From: Tommi Reiman Date: Tue, 14 Mar 2023 20:58:07 +0200 Subject: [PATCH 29/50] offer both swagger & openapi docs in ui --- examples/http-swagger/src/example/server.clj | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/http-swagger/src/example/server.clj b/examples/http-swagger/src/example/server.clj index b5e1fc58..072eb79d 100644 --- a/examples/http-swagger/src/example/server.clj +++ b/examples/http-swagger/src/example/server.clj @@ -43,7 +43,7 @@ [["/swagger.json" {:get {:no-doc true :swagger {:info {:title "my-api" - :description "with reitit-http"} + :description "swagger-docs with reitit-http"} ;; used in /secure APIs below :securityDefinitions {"auth" {:type :apiKey :in :header @@ -52,7 +52,7 @@ ["/openapi.json" {:get {:no-doc true :openapi {:info {:title "my-api" - :description "with reitit-http" + :description "openap-docs with reitit-http" :version "0.0.1"} ;; used in /secure APIs below :components {:securitySchemes {"auth" {:type :apiKey @@ -174,6 +174,9 @@ (swagger-ui/create-swagger-ui-handler {:path "/" :config {:validatorUrl nil + :urls [{:name "swagger", :url "swagger.json"} + {:name "openapi", :url "openapi.json"}] + :urls.primaryName "openapi" :operationsSorter "alpha"}}) (ring/create-default-handler)) {:executor sieppari/executor})) From 23b2719be5171bf705cfc9a5ce77b5b1e2d3ce64 Mon Sep 17 00:00:00 2001 From: Tommi Reiman Date: Tue, 14 Mar 2023 21:09:03 +0200 Subject: [PATCH 30/50] add version to swagger api --- examples/http-swagger/src/example/server.clj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/http-swagger/src/example/server.clj b/examples/http-swagger/src/example/server.clj index 072eb79d..5f3d876c 100644 --- a/examples/http-swagger/src/example/server.clj +++ b/examples/http-swagger/src/example/server.clj @@ -43,7 +43,8 @@ [["/swagger.json" {:get {:no-doc true :swagger {:info {:title "my-api" - :description "swagger-docs with reitit-http"} + :description "swagger-docs with reitit-http" + :version "0.0.1"} ;; used in /secure APIs below :securityDefinitions {"auth" {:type :apiKey :in :header From 6f72acdf320ae2c454d3fab7eb60fc4d36dd0533 Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Wed, 15 Mar 2023 09:23:29 +0200 Subject: [PATCH 31/50] doc: fix example in openapi-feature docstring --- modules/reitit-openapi/src/reitit/openapi.cljc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/reitit-openapi/src/reitit/openapi.cljc b/modules/reitit-openapi/src/reitit/openapi.cljc index 5402c75c..89bfd3df 100644 --- a/modules/reitit-openapi/src/reitit/openapi.cljc +++ b/modules/reitit-openapi/src/reitit/openapi.cljc @@ -55,7 +55,7 @@ [\"/openapi.json\" {:get {:no-doc true :openapi {:info {:title \"my-api\"}} - :handler reitit.openapi/openapi-spec-handler}}] + :handler (reitit.openapi/create-openapi-handler)}}] [\"/plus\" {:get {:openapi {:tags \"math\"} From 66e7c9e1c8e559b0fd85b91c9e08a7ec03bdc776 Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Wed, 15 Mar 2023 13:19:01 +0200 Subject: [PATCH 32/50] test: nicer handling of missing :additionalProperties for spec --- test/cljc/reitit/openapi_test.clj | 55 +++++++++++++++++-------------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/test/cljc/reitit/openapi_test.clj b/test/cljc/reitit/openapi_test.clj index a1c944d2..6ae134b2 100644 --- a/test/cljc/reitit/openapi_test.clj +++ b/test/cljc/reitit/openapi_test.clj @@ -408,18 +408,21 @@ (get-in [:paths "/parameters" :post :parameters]) normalize)))) (testing "body parameter" - (is (match? {:schema {:type "object" - :properties {:b {:type "string"}} - #_#_:additionalProperties false ;; not present for spec - :required ["b"]}} + (is (match? {:schema (merge {:type "object" + :properties {:b {:type "string"}} + :required ["b"]} + ;; spec outputs open schemas + (when-not (#{#'spec/coercion} coercion) + {:additionalProperties false}))} (-> spec (get-in [:paths "/parameters" :post :requestBody :content "application/json"]) normalize)))) (testing "body response" - (is (match? {:schema {:type "object" - :properties {:ok {:type "string"}} - #_#_:additionalProperties false ;; not present for spec - :required ["ok"]}} + (is (match? {:schema (merge {:type "object" + :properties {:ok {:type "string"}} + :required ["ok"]} + (when-not (#{#'spec/coercion} coercion) + {:additionalProperties false}))} (-> spec (get-in [:paths "/parameters" :post :responses 200 :content "application/json"]) normalize)))) @@ -458,32 +461,36 @@ app :body)] (testing "body parameter" - (is (match? {:schema {:type "object" - :properties {:b {:type "string"}} - #_#_:additionalProperties false ;; not present for spec - :required ["b"]}} + (is (match? {:schema (merge {:type "object" + :properties {:b {:type "string"}} + :required ["b"]} + (when-not (#{#'spec/coercion} coercion) + {:additionalProperties false}))} (-> spec (get-in [:paths "/parameters" :post :requestBody :content "application/json"]) normalize))) - (is (match? {:schema {:type "object" - :properties {:c {:type "string"}} - #_#_:additionalProperties false ;; not present for spec - :required ["c"]}} + (is (match? {:schema (merge {:type "object" + :properties {:c {:type "string"}} + :required ["c"]} + (when-not (#{#'spec/coercion} coercion) + {:additionalProperties false}))} (-> spec (get-in [:paths "/parameters" :post :requestBody :content "application/edn"]) normalize)))) (testing "body response" - (is (match? {:schema {:type "object" - :properties {:ok {:type "string"}} - #_#_:additionalProperties false ;; not present for spec - :required ["ok"]}} + (is (match? {:schema (merge {:type "object" + :properties {:ok {:type "string"}} + :required ["ok"]} + (when-not (#{#'spec/coercion} coercion) + {:additionalProperties false}))} (-> spec (get-in [:paths "/parameters" :post :responses 200 :content "application/json"]) normalize))) - (is (match? {:schema {:type "object" - :properties {:edn {:type "string"}} - #_#_:additionalProperties false ;; not present for spec - :required ["edn"]}} + (is (match? {:schema (merge {:type "object" + :properties {:edn {:type "string"}} + :required ["edn"]} + (when-not (#{#'spec/coercion} coercion) + {:additionalProperties false}))} (-> spec (get-in [:paths "/parameters" :post :responses 200 :content "application/edn"]) normalize)))) From 5107658c252c798bb4e36d960ad41c76a57a2c2a Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Wed, 15 Mar 2023 13:20:02 +0200 Subject: [PATCH 33/50] fix: typo on examples/http-swagger --- examples/http-swagger/src/example/server.clj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/http-swagger/src/example/server.clj b/examples/http-swagger/src/example/server.clj index 5f3d876c..79bcd15b 100644 --- a/examples/http-swagger/src/example/server.clj +++ b/examples/http-swagger/src/example/server.clj @@ -53,7 +53,7 @@ ["/openapi.json" {:get {:no-doc true :openapi {:info {:title "my-api" - :description "openap-docs with reitit-http" + :description "openapi3-docs with reitit-http" :version "0.0.1"} ;; used in /secure APIs below :components {:securitySchemes {"auth" {:type :apiKey @@ -175,9 +175,9 @@ (swagger-ui/create-swagger-ui-handler {:path "/" :config {:validatorUrl nil - :urls [{:name "swagger", :url "swagger.json"} - {:name "openapi", :url "openapi.json"}] - :urls.primaryName "openapi" + :urls [{:name "swagger", :url "swagger.json"} + {:name "openapi", :url "openapi.json"}] + :urls.primaryName "openapi" :operationsSorter "alpha"}}) (ring/create-default-handler)) {:executor sieppari/executor})) From 41c4d78823b1decfdd77359c8588468ddb9ff603 Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Wed, 15 Mar 2023 13:21:14 +0200 Subject: [PATCH 34/50] chore: reformat http-swagger example --- examples/http-swagger/src/example/server.clj | 266 +++++++++---------- 1 file changed, 133 insertions(+), 133 deletions(-) diff --git a/examples/http-swagger/src/example/server.clj b/examples/http-swagger/src/example/server.clj index 79bcd15b..c7251d9c 100644 --- a/examples/http-swagger/src/example/server.clj +++ b/examples/http-swagger/src/example/server.clj @@ -39,148 +39,148 @@ (def app (http/ring-handler - (http/router - [["/swagger.json" - {:get {:no-doc true - :swagger {:info {:title "my-api" - :description "swagger-docs with reitit-http" - :version "0.0.1"} - ;; used in /secure APIs below - :securityDefinitions {"auth" {:type :apiKey - :in :header - :name "Example-Api-Key"}}} - :handler (swagger/create-swagger-handler)}}] - ["/openapi.json" - {:get {:no-doc true - :openapi {:info {:title "my-api" - :description "openapi3-docs with reitit-http" - :version "0.0.1"} - ;; used in /secure APIs below - :components {:securitySchemes {"auth" {:type :apiKey - :in :header - :name "Example-Api-Key"}}}} - :handler (openapi/create-openapi-handler)}}] + (http/router + [["/swagger.json" + {:get {:no-doc true + :swagger {:info {:title "my-api" + :description "swagger-docs with reitit-http" + :version "0.0.1"} + ;; used in /secure APIs below + :securityDefinitions {"auth" {:type :apiKey + :in :header + :name "Example-Api-Key"}}} + :handler (swagger/create-swagger-handler)}}] + ["/openapi.json" + {:get {:no-doc true + :openapi {:info {:title "my-api" + :description "openapi3-docs with reitit-http" + :version "0.0.1"} + ;; used in /secure APIs below + :components {:securitySchemes {"auth" {:type :apiKey + :in :header + :name "Example-Api-Key"}}}} + :handler (openapi/create-openapi-handler)}}] - ["/files" - {:tags ["files"]} + ["/files" + {:tags ["files"]} - ["/upload" - {:post {:summary "upload a file" - :parameters {:multipart {:file multipart/temp-file-part}} - :responses {200 {:body {:name string?, :size int?}}} - :handler (fn [{{{:keys [file]} :multipart} :parameters}] - {:status 200 - :body {:name (:filename file) - :size (:size file)}})}}] + ["/upload" + {:post {:summary "upload a file" + :parameters {:multipart {:file multipart/temp-file-part}} + :responses {200 {:body {:name string?, :size int?}}} + :handler (fn [{{{:keys [file]} :multipart} :parameters}] + {:status 200 + :body {:name (:filename file) + :size (:size file)}})}}] - ["/download" - {:get {:summary "downloads a file" - :swagger {:produces ["image/png"]} - :handler (fn [_] + ["/download" + {:get {:summary "downloads a file" + :swagger {:produces ["image/png"]} + :handler (fn [_] + {:status 200 + :headers {"Content-Type" "image/png"} + :body (io/input-stream + (io/resource "reitit.png"))})}}]] + + ["/async" + {:get {:tags ["async"] + :summary "fetches random users asynchronously over the internet" + :parameters {:query (s/keys :req-un [::results] :opt-un [::seed])} + :responses {200 {:body any?}} + :handler (fn [{{{:keys [seed results]} :query} :parameters}] + (d/chain + (aleph/get + "https://randomuser.me/api/" + {:query-params {:seed seed, :results results}}) + :body + (partial m/decode "application/json") + :results + (fn [results] {:status 200 - :headers {"Content-Type" "image/png"} - :body (io/input-stream - (io/resource "reitit.png"))})}}]] + :body results})))}}] - ["/async" - {:get {:tags ["async"] - :summary "fetches random users asynchronously over the internet" - :parameters {:query (s/keys :req-un [::results] :opt-un [::seed])} - :responses {200 {:body any?}} - :handler (fn [{{{:keys [seed results]} :query} :parameters}] - (d/chain - (aleph/get - "https://randomuser.me/api/" - {:query-params {:seed seed, :results results}}) - :body - (partial m/decode "application/json") - :results - (fn [results] - {:status 200 - :body results})))}}] + ["/math" + {:tags ["math"]} - ["/math" - {:tags ["math"]} + ["/plus" + {:get {:summary "plus with data-spec query parameters" + :parameters {:query {:x int?, :y int?}} + :responses {200 {:body {:total pos-int?}}} + :handler (fn [{{{:keys [x y]} :query} :parameters}] + {:status 200 + :body {:total (+ x y)}})} + :post {:summary "plus with data-spec body parameters" + :parameters {:body {:x int?, :y int?}} + :responses {200 {:body {:total int?}}} + :handler (fn [{{{:keys [x y]} :body} :parameters}] + {:status 200 + :body {:total (+ x y)}})}}] - ["/plus" - {:get {:summary "plus with data-spec query parameters" - :parameters {:query {:x int?, :y int?}} - :responses {200 {:body {:total pos-int?}}} - :handler (fn [{{{:keys [x y]} :query} :parameters}] + ["/minus" + {:get {:summary "minus with clojure.spec query parameters" + :parameters {:query (s/keys :req-un [::x ::y])} + :responses {200 {:body (s/keys :req-un [::total])}} + :handler (fn [{{{:keys [x y]} :query} :parameters}] + {:status 200 + :body {:total (- x y)}})} + :post {:summary "minus with clojure.spec body parameters" + :parameters {:body (s/keys :req-un [::x ::y])} + :responses {200 {:body (s/keys :req-un [::total])}} + :handler (fn [{{{:keys [x y]} :body} :parameters}] + {:status 200 + :body {:total (- x y)}})}}]] + ["/secure" + {:tags ["secure"] + :openapi {:security [{"auth" []}]} + :swagger {:security [{"auth" []}]}} + ["/get" + {:get {:summary "endpoint authenticated with a header" + :responses {200 {:body {:secret string?}} + 401 {:body {:error string?}}} + :handler (fn [request] + ;; In a real app authentication would be handled by middleware + (if (= "secret" (get-in request [:headers "example-api-key"])) {:status 200 - :body {:total (+ x y)}})} - :post {:summary "plus with data-spec body parameters" - :parameters {:body {:x int?, :y int?}} - :responses {200 {:body {:total int?}}} - :handler (fn [{{{:keys [x y]} :body} :parameters}] - {:status 200 - :body {:total (+ x y)}})}}] + :body {:secret "I am a marmot"}} + {:status 401 + :body {:error "unauthorized"}}))}}]]] - ["/minus" - {:get {:summary "minus with clojure.spec query parameters" - :parameters {:query (s/keys :req-un [::x ::y])} - :responses {200 {:body (s/keys :req-un [::total])}} - :handler (fn [{{{:keys [x y]} :query} :parameters}] - {:status 200 - :body {:total (- x y)}})} - :post {:summary "minus with clojure.spec body parameters" - :parameters {:body (s/keys :req-un [::x ::y])} - :responses {200 {:body (s/keys :req-un [::total])}} - :handler (fn [{{{:keys [x y]} :body} :parameters}] - {:status 200 - :body {:total (- x y)}})}}]] - ["/secure" - {:tags ["secure"] - :openapi {:security [{"auth" []}]} - :swagger {:security [{"auth" []}]}} - ["/get" - {:get {:summary "endpoint authenticated with a header" - :responses {200 {:body {:secret string?}} - 401 {:body {:error string?}}} - :handler (fn [request] - ;; In a real app authentication would be handled by middleware - (if (= "secret" (get-in request [:headers "example-api-key"])) - {:status 200 - :body {:secret "I am a marmot"}} - {:status 401 - :body {:error "unauthorized"}}))}}]]] - - {;:reitit.interceptor/transform dev/print-context-diffs ;; pretty context diffs - ;;:validate spec/validate ;; enable spec validation for route data - ;;:reitit.spec/wrap spell/closed ;; strict top-level validation - :exception pretty/exception - :data {:coercion reitit.coercion.spec/coercion - :muuntaja m/instance - :interceptors [;; swagger feature - swagger/swagger-feature - ;; openapi feature - openapi/openapi-feature - ;; query-params & form-params - (parameters/parameters-interceptor) - ;; content-negotiation - (muuntaja/format-negotiate-interceptor) - ;; encoding response body - (muuntaja/format-response-interceptor) - ;; exception handling - (exception/exception-interceptor) - ;; decoding request body - (muuntaja/format-request-interceptor) - ;; coercing response bodys - (coercion/coerce-response-interceptor) - ;; coercing request parameters - (coercion/coerce-request-interceptor) - ;; multipart - (multipart/multipart-interceptor)]}}) - (ring/routes - (swagger-ui/create-swagger-ui-handler - {:path "/" - :config {:validatorUrl nil - :urls [{:name "swagger", :url "swagger.json"} - {:name "openapi", :url "openapi.json"}] - :urls.primaryName "openapi" - :operationsSorter "alpha"}}) - (ring/create-default-handler)) - {:executor sieppari/executor})) + {;:reitit.interceptor/transform dev/print-context-diffs ;; pretty context diffs + ;;:validate spec/validate ;; enable spec validation for route data + ;;:reitit.spec/wrap spell/closed ;; strict top-level validation + :exception pretty/exception + :data {:coercion reitit.coercion.spec/coercion + :muuntaja m/instance + :interceptors [;; swagger feature + swagger/swagger-feature + ;; openapi feature + openapi/openapi-feature + ;; query-params & form-params + (parameters/parameters-interceptor) + ;; content-negotiation + (muuntaja/format-negotiate-interceptor) + ;; encoding response body + (muuntaja/format-response-interceptor) + ;; exception handling + (exception/exception-interceptor) + ;; decoding request body + (muuntaja/format-request-interceptor) + ;; coercing response bodys + (coercion/coerce-response-interceptor) + ;; coercing request parameters + (coercion/coerce-request-interceptor) + ;; multipart + (multipart/multipart-interceptor)]}}) + (ring/routes + (swagger-ui/create-swagger-ui-handler + {:path "/" + :config {:validatorUrl nil + :urls [{:name "swagger", :url "swagger.json"} + {:name "openapi", :url "openapi.json"}] + :urls.primaryName "openapi" + :operationsSorter "alpha"}}) + (ring/create-default-handler)) + {:executor sieppari/executor})) (defn start [] (jetty/run-jetty #'app {:port 3000, :join? false, :async true}) From bcd12d9f31089b07139b6c146f7e2ba94634cf32 Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Fri, 10 Mar 2023 08:57:02 +0200 Subject: [PATCH 35/50] refactor: push openapi/openapi-spec call inside merge to make reitit.coercion.spec match reitit.coercion.schema --- .../reitit-spec/src/reitit/coercion/spec.cljc | 67 +++++++++---------- 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/modules/reitit-spec/src/reitit/coercion/spec.cljc b/modules/reitit-spec/src/reitit/coercion/spec.cljc index 31050a01..d453dd61 100644 --- a/modules/reitit-spec/src/reitit/coercion/spec.cljc +++ b/modules/reitit-spec/src/reitit/coercion/spec.cljc @@ -107,40 +107,39 @@ (if (:schema $) (update $ :schema #(coercion/-compile-model this % nil)) $))]))}))) - :openapi (openapi/openapi-spec - (merge - (when (seq (dissoc parameters :body :request)) - {::openapi/parameters - (into (empty parameters) - (for [[k v] (dissoc parameters :body :request)] - [k (coercion/-compile-model this v nil)]))}) - (when (:body parameters) - {:requestBody (openapi/openapi-spec - {::openapi/content (zipmap content-types (repeat (coercion/-compile-model this (:body parameters) nil)))})}) - (when (:request parameters) - {:requestBody (openapi/openapi-spec - {::openapi/content (merge - (when-let [default (get-in parameters [:request :body])] - (zipmap content-types (repeat (coercion/-compile-model this default nil)))) - (into {} - (for [[format model] (:content (:request parameters))] - [format (coercion/-compile-model this model nil)])))})}) - (when responses - {:responses - (into - (empty responses) - (for [[k {:keys [body content] :as response}] responses] - [k (merge - (select-keys response [:description]) - (when (or body content) - (openapi/openapi-spec - {::openapi/content (merge - (when body - (zipmap content-types (repeat (coercion/-compile-model this (:body response) nil)))) - (when response - (into {} - (for [[format model] (:content response)] - [format (coercion/-compile-model this model nil)]))))})))]))}))) + :openapi (merge + (when (seq (dissoc parameters :body :request)) + (openapi/openapi-spec {::openapi/parameters + (into (empty parameters) + (for [[k v] (dissoc parameters :body :request)] + [k (coercion/-compile-model this v nil)]))})) + (when (:body parameters) + {:requestBody (openapi/openapi-spec + {::openapi/content (zipmap content-types (repeat (coercion/-compile-model this (:body parameters) nil)))})}) + (when (:request parameters) + {:requestBody (openapi/openapi-spec + {::openapi/content (merge + (when-let [default (get-in parameters [:request :body])] + (zipmap content-types (repeat (coercion/-compile-model this default nil)))) + (into {} + (for [[format model] (:content (:request parameters))] + [format (coercion/-compile-model this model nil)])))})}) + (when responses + {:responses + (into + (empty responses) + (for [[k {:keys [body content] :as response}] responses] + [k (merge + (select-keys response [:description]) + (when (or body content) + (openapi/openapi-spec + {::openapi/content (merge + (when body + (zipmap content-types (repeat (coercion/-compile-model this (:body response) nil)))) + (when response + (into {} + (for [[format model] (:content response)] + [format (coercion/-compile-model this model nil)]))))})))]))})) (throw (ex-info (str "Can't produce Spec apidocs for " specification) From d0ff64df57c5c31ea6280ab5f0d6c92213ebd197 Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Wed, 15 Mar 2023 16:01:57 +0200 Subject: [PATCH 36/50] test: simplify openapi_test.clj a bit more --- test/cljc/reitit/openapi_test.clj | 74 +++++++++++++++---------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/test/cljc/reitit/openapi_test.clj b/test/cljc/reitit/openapi_test.clj index 6ae134b2..3db5533f 100644 --- a/test/cljc/reitit/openapi_test.clj +++ b/test/cljc/reitit/openapi_test.clj @@ -408,23 +408,23 @@ (get-in [:paths "/parameters" :post :parameters]) normalize)))) (testing "body parameter" - (is (match? {:schema (merge {:type "object" - :properties {:b {:type "string"}} - :required ["b"]} - ;; spec outputs open schemas - (when-not (#{#'spec/coercion} coercion) - {:additionalProperties false}))} + (is (match? (merge {:type "object" + :properties {:b {:type "string"}} + :required ["b"]} + ;; spec outputs open schemas + (when-not (#{#'spec/coercion} coercion) + {:additionalProperties false})) (-> spec - (get-in [:paths "/parameters" :post :requestBody :content "application/json"]) + (get-in [:paths "/parameters" :post :requestBody :content "application/json" :schema]) normalize)))) (testing "body response" - (is (match? {:schema (merge {:type "object" - :properties {:ok {:type "string"}} - :required ["ok"]} - (when-not (#{#'spec/coercion} coercion) - {:additionalProperties false}))} + (is (match? (merge {:type "object" + :properties {:ok {:type "string"}} + :required ["ok"]} + (when-not (#{#'spec/coercion} coercion) + {:additionalProperties false})) (-> spec - (get-in [:paths "/parameters" :post :responses 200 :content "application/json"]) + (get-in [:paths "/parameters" :post :responses 200 :content "application/json" :schema]) normalize)))) (testing "spec is valid" (is (nil? (validate spec)))))))) @@ -461,38 +461,38 @@ app :body)] (testing "body parameter" - (is (match? {:schema (merge {:type "object" - :properties {:b {:type "string"}} - :required ["b"]} - (when-not (#{#'spec/coercion} coercion) - {:additionalProperties false}))} + (is (match? (merge {:type "object" + :properties {:b {:type "string"}} + :required ["b"]} + (when-not (#{#'spec/coercion} coercion) + {:additionalProperties false})) (-> spec - (get-in [:paths "/parameters" :post :requestBody :content "application/json"]) + (get-in [:paths "/parameters" :post :requestBody :content "application/json" :schema]) normalize))) - (is (match? {:schema (merge {:type "object" - :properties {:c {:type "string"}} - :required ["c"]} - (when-not (#{#'spec/coercion} coercion) - {:additionalProperties false}))} + (is (match? (merge {:type "object" + :properties {:c {:type "string"}} + :required ["c"]} + (when-not (#{#'spec/coercion} coercion) + {:additionalProperties false})) (-> spec - (get-in [:paths "/parameters" :post :requestBody :content "application/edn"]) + (get-in [:paths "/parameters" :post :requestBody :content "application/edn" :schema]) normalize)))) (testing "body response" - (is (match? {:schema (merge {:type "object" - :properties {:ok {:type "string"}} - :required ["ok"]} - (when-not (#{#'spec/coercion} coercion) - {:additionalProperties false}))} + (is (match? (merge {:type "object" + :properties {:ok {:type "string"}} + :required ["ok"]} + (when-not (#{#'spec/coercion} coercion) + {:additionalProperties false})) (-> spec - (get-in [:paths "/parameters" :post :responses 200 :content "application/json"]) + (get-in [:paths "/parameters" :post :responses 200 :content "application/json" :schema]) normalize))) - (is (match? {:schema (merge {:type "object" - :properties {:edn {:type "string"}} - :required ["edn"]} - (when-not (#{#'spec/coercion} coercion) - {:additionalProperties false}))} + (is (match? (merge {:type "object" + :properties {:edn {:type "string"}} + :required ["edn"]} + (when-not (#{#'spec/coercion} coercion) + {:additionalProperties false})) (-> spec - (get-in [:paths "/parameters" :post :responses 200 :content "application/edn"]) + (get-in [:paths "/parameters" :post :responses 200 :content "application/edn" :schema]) normalize)))) (testing "validation" (let [query {:request-method :post From f322597c04375e0d0032310e9d99abfc6eaf96c2 Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Wed, 15 Mar 2023 10:22:26 +0200 Subject: [PATCH 37/50] feat: openapi3 multipart support for spec --- .../reitit/http/interceptors/multipart.clj | 8 +++-- .../reitit-spec/src/reitit/coercion/spec.cljc | 8 ++++- test/cljc/reitit/openapi_test.clj | 34 +++++++++++++++++++ 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/modules/reitit-interceptors/src/reitit/http/interceptors/multipart.clj b/modules/reitit-interceptors/src/reitit/http/interceptors/multipart.clj index d616c689..4a2d4ba1 100644 --- a/modules/reitit-interceptors/src/reitit/http/interceptors/multipart.clj +++ b/modules/reitit-interceptors/src/reitit/http/interceptors/multipart.clj @@ -19,13 +19,17 @@ "Spec for file param created by ring.middleware.multipart-params.temp-file store." (st/spec {:spec (s/keys :req-un [::filename ::content-type ::tempfile ::size]) - :swagger/type "file"})) + :swagger/type "file" + :openapi {:type "string" + :format "binary"}})) (def bytes-part "Spec for file param created by ring.middleware.multipart-params.byte-array store." (st/spec {:spec (s/keys :req-un [::filename ::content-type ::bytes]) - :swagger/type "file"})) + :swagger/type "file" + :openapi {:type "string" + :format "binary"}})) (defn- coerced-request [request coercers] (if-let [coerced (if coercers (coercion/coerce-request coercers request))] diff --git a/modules/reitit-spec/src/reitit/coercion/spec.cljc b/modules/reitit-spec/src/reitit/coercion/spec.cljc index d453dd61..d5edd78b 100644 --- a/modules/reitit-spec/src/reitit/coercion/spec.cljc +++ b/modules/reitit-spec/src/reitit/coercion/spec.cljc @@ -108,7 +108,7 @@ (update $ :schema #(coercion/-compile-model this % nil)) $))]))}))) :openapi (merge - (when (seq (dissoc parameters :body :request)) + (when (seq (dissoc parameters :body :request :multipart)) (openapi/openapi-spec {::openapi/parameters (into (empty parameters) (for [[k v] (dissoc parameters :body :request)] @@ -124,6 +124,12 @@ (into {} (for [[format model] (:content (:request parameters))] [format (coercion/-compile-model this model nil)])))})}) + (when (:multipart parameters) + {:requestBody + (openapi/openapi-spec + {::openapi/content + {"multipart/form-data" + (coercion/-compile-model this (:multipart parameters) nil)}})}) (when responses {:responses (into diff --git a/test/cljc/reitit/openapi_test.clj b/test/cljc/reitit/openapi_test.clj index 3db5533f..0216ac8e 100644 --- a/test/cljc/reitit/openapi_test.clj +++ b/test/cljc/reitit/openapi_test.clj @@ -8,6 +8,7 @@ [reitit.coercion.malli :as malli] [reitit.coercion.schema :as schema] [reitit.coercion.spec :as spec] + [reitit.http.interceptors.multipart] [reitit.openapi :as openapi] [reitit.ring :as ring] [reitit.ring.spec] @@ -429,6 +430,39 @@ (testing "spec is valid" (is (nil? (validate spec)))))))) +(deftest multipart-test + (doseq [[coercion file-schema] + [#_[#'malli/coercion (fn [nom] [:map [nom :string]])] + #_[#'schema/coercion (fn [nom] {nom s/Str})] + [#'spec/coercion reitit.http.interceptors.multipart/bytes-part]]] + (testing coercion + (let [app (ring/ring-handler + (ring/router + [["/upload" + {:post {:decription "upload" + :coercion @coercion + :parameters {:multipart {:file file-schema}} + :handler identity}}] + ["/openapi.json" + {:get {:handler (openapi/create-openapi-handler) + :openapi {:info {:title "" :version "0.0.1"}} + :no-doc true}}]] + {:data {:middleware [openapi/openapi-feature]}})) + spec (-> {:request-method :get + :uri "/openapi.json"} + app + :body)] + (testing "multipart body" + (is (= {:requestBody {:content {"multipart/form-data" {:schema {:type "object" + :properties {"file" {:type "string" + :format "binary"}} + :required ["file"]}}}}} + (-> spec + (get-in [:paths "/upload" :post]) + #_normalize)))) + (testing "spec is valid" + (is (nil? (validate spec)))))))) + (deftest per-content-type-test (doseq [[coercion ->schema] [[#'malli/coercion (fn [nom] [:map [nom :string]])] From 1c65f533cfaacf931c6757599ac0aa3ca13219ca Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Wed, 15 Mar 2023 15:48:53 +0200 Subject: [PATCH 38/50] feat: openapi3 multipart support for malli --- .../src/reitit/coercion/malli.cljc | 12 +++++++++-- test/cljc/reitit/openapi_test.clj | 21 ++++++++++++------- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/modules/reitit-malli/src/reitit/coercion/malli.cljc b/modules/reitit-malli/src/reitit/coercion/malli.cljc index 9b66f8fa..99226230 100644 --- a/modules/reitit-malli/src/reitit/coercion/malli.cljc +++ b/modules/reitit-malli/src/reitit/coercion/malli.cljc @@ -135,8 +135,8 @@ (defn -get-apidocs-openapi [coercion {:keys [parameters responses content-types] :or {content-types ["application/json"]}} options] - (let [{:keys [body request]} parameters - parameters (dissoc parameters :request :body) + (let [{:keys [body request multipart]} parameters + parameters (dissoc parameters :request :body :multipart) ->schema-object (fn [schema opts] (let [current-opts (merge options opts)] (json-schema/transform (coercion/-compile-model coercion schema current-opts) @@ -184,6 +184,14 @@ :content-type content-type})] [content-type {:schema schema}]))) (:content request)))}}) + (when multipart + {:requestBody + {:content + {"multipart/form-data" + {:schema + (->schema-object multipart {:in :requestBody + :type :schema + :content-type "multipart/form-data"})}}}}) (when responses {:responses (into {} diff --git a/test/cljc/reitit/openapi_test.clj b/test/cljc/reitit/openapi_test.clj index 0216ac8e..3f789a47 100644 --- a/test/cljc/reitit/openapi_test.clj +++ b/test/cljc/reitit/openapi_test.clj @@ -432,7 +432,11 @@ (deftest multipart-test (doseq [[coercion file-schema] - [#_[#'malli/coercion (fn [nom] [:map [nom :string]])] + [[#'malli/coercion [:map {:json-schema {:type "string" + :format "binary"}} + [:filename :string] + [:content-type :string] + [:bytes :int]]] #_[#'schema/coercion (fn [nom] {nom s/Str})] [#'spec/coercion reitit.http.interceptors.multipart/bytes-part]]] (testing coercion @@ -453,13 +457,16 @@ app :body)] (testing "multipart body" - (is (= {:requestBody {:content {"multipart/form-data" {:schema {:type "object" - :properties {"file" {:type "string" - :format "binary"}} - :required ["file"]}}}}} + (is (nil? (get-in spec [:paths "/upload" :post :parameters]))) + (is (= (merge {:type "object" + :properties {:file {:type "string" + :format "binary"}} + :required ["file"]} + (when-not (= #'spec/coercion coercion) + {:additionalProperties false})) (-> spec - (get-in [:paths "/upload" :post]) - #_normalize)))) + (get-in [:paths "/upload" :post :requestBody :content "multipart/form-data" :schema]) + normalize)))) (testing "spec is valid" (is (nil? (validate spec)))))))) From 60f53d7e7cdcab77430782a8341e78aac7c6afb0 Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Wed, 15 Mar 2023 17:32:49 +0200 Subject: [PATCH 39/50] deps: upgrade schema-tools --- project.clj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/project.clj b/project.clj index 72936964..eebcf7d9 100644 --- a/project.clj +++ b/project.clj @@ -33,7 +33,7 @@ [metosin/reitit-pedestal "0.6.0"] [metosin/ring-swagger-ui "4.15.5"] [metosin/spec-tools "0.10.5"] - [metosin/schema-tools "0.12.3"] + [metosin/schema-tools "0.13.0"] [metosin/muuntaja "0.6.8"] [metosin/jsonista "0.3.7"] [metosin/sieppari "0.0.0-alpha13"] @@ -86,7 +86,7 @@ [org.clojure/clojurescript "1.10.773"] ;; modules dependencies - [metosin/schema-tools "0.12.3"] + [metosin/schema-tools "0.13.0"] [metosin/spec-tools "0.10.5"] [metosin/muuntaja "0.6.8"] [metosin/sieppari "0.0.0-alpha13"] From acbcec1ed902f11a6d07595a2a531b6d7b0829ef Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Wed, 15 Mar 2023 14:22:21 +0200 Subject: [PATCH 40/50] feat: openapi3 multipart support for schema --- modules/reitit-schema/src/reitit/coercion/schema.cljc | 6 +++++- test/cljc/reitit/openapi_test.clj | 7 ++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/modules/reitit-schema/src/reitit/coercion/schema.cljc b/modules/reitit-schema/src/reitit/coercion/schema.cljc index 07dc5ce6..ce66e153 100644 --- a/modules/reitit-schema/src/reitit/coercion/schema.cljc +++ b/modules/reitit-schema/src/reitit/coercion/schema.cljc @@ -70,7 +70,7 @@ (update $ :schema #(coercion/-compile-model this % nil)) $))]))}))) :openapi (merge - (when (seq (dissoc parameters :body :request)) + (when (seq (dissoc parameters :body :request :multipart)) (openapi/openapi-spec {::openapi/parameters (into (empty parameters) @@ -85,6 +85,10 @@ (when-let [default (get-in parameters [:request :body])] (zipmap content-types (repeat default))) (:content (:request parameters)))})}) + (when (:multipart parameters) + {:requestBody + (openapi/openapi-spec + {::openapi/content {"multipart/form-data" (:multipart parameters)}})}) (when responses {:responses (into diff --git a/test/cljc/reitit/openapi_test.clj b/test/cljc/reitit/openapi_test.clj index 3f789a47..80d18f8b 100644 --- a/test/cljc/reitit/openapi_test.clj +++ b/test/cljc/reitit/openapi_test.clj @@ -15,6 +15,7 @@ [reitit.ring.coercion :as rrc] [reitit.swagger-ui :as swagger-ui] [schema.core :as s] + [schema-tools.core] [spec-tools.data-spec :as ds])) (defn validate @@ -437,7 +438,11 @@ [:filename :string] [:content-type :string] [:bytes :int]]] - #_[#'schema/coercion (fn [nom] {nom s/Str})] + [#'schema/coercion (schema-tools.core/schema {:filename s/Str + :content-type s/Str + :bytes s/Num} + {:openapi {:type "string" + :format "binary"}})] [#'spec/coercion reitit.http.interceptors.multipart/bytes-part]]] (testing coercion (let [app (ring/ring-handler From de2d810b7cae7f4fe062d5f6817139b127a2f006 Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Wed, 15 Mar 2023 16:51:01 +0200 Subject: [PATCH 41/50] test: multiple parts in multipart-test --- test/cljc/reitit/openapi_test.clj | 38 +++++++++++++++++++------------ 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/test/cljc/reitit/openapi_test.clj b/test/cljc/reitit/openapi_test.clj index 80d18f8b..7e9bfcda 100644 --- a/test/cljc/reitit/openapi_test.clj +++ b/test/cljc/reitit/openapi_test.clj @@ -432,25 +432,32 @@ (is (nil? (validate spec)))))))) (deftest multipart-test - (doseq [[coercion file-schema] - [[#'malli/coercion [:map {:json-schema {:type "string" - :format "binary"}} - [:filename :string] - [:content-type :string] - [:bytes :int]]] - [#'schema/coercion (schema-tools.core/schema {:filename s/Str - :content-type s/Str - :bytes s/Num} - {:openapi {:type "string" - :format "binary"}})] - [#'spec/coercion reitit.http.interceptors.multipart/bytes-part]]] + (doseq [[coercion file-schema string-schema] + [[#'malli/coercion + [:map {:json-schema {:type "string" + :format "binary"}} + [:filename :string] + [:content-type :string] + [:bytes :int]] + :string] + [#'schema/coercion + (schema-tools.core/schema {:filename s/Str + :content-type s/Str + :bytes s/Num} + {:openapi {:type "string" + :format "binary"}}) + s/Str] + [#'spec/coercion + reitit.http.interceptors.multipart/bytes-part + string?]]] (testing coercion (let [app (ring/ring-handler (ring/router [["/upload" {:post {:decription "upload" :coercion @coercion - :parameters {:multipart {:file file-schema}} + :parameters {:multipart {:file file-schema + :more string-schema}} :handler identity}}] ["/openapi.json" {:get {:handler (openapi/create-openapi-handler) @@ -465,8 +472,9 @@ (is (nil? (get-in spec [:paths "/upload" :post :parameters]))) (is (= (merge {:type "object" :properties {:file {:type "string" - :format "binary"}} - :required ["file"]} + :format "binary"} + :more {:type "string"}} + :required ["file" "more"]} (when-not (= #'spec/coercion coercion) {:additionalProperties false})) (-> spec From 83b747c7c66fd3d72d00d8c1a6d3596622c769f6 Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Thu, 16 Mar 2023 09:39:34 +0200 Subject: [PATCH 42/50] doc: mention :multipart in doc/ring/coercion.md --- doc/ring/coercion.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/doc/ring/coercion.md b/doc/ring/coercion.md index 930c10df..8de57c1a 100644 --- a/doc/ring/coercion.md +++ b/doc/ring/coercion.md @@ -4,14 +4,15 @@ Basic coercion is explained in detail [in the Coercion Guide](../coercion/coerci The following request parameters are currently supported: -| type | request source | -|------------|--------------------------------------------------| -| `:query` | `:query-params` | -| `:body` | `:body-params` | -| `:request` | `:body-params`, allows per-content-type coercion | -| `:form` | `:form-params` | -| `:header` | `:header-params` | -| `:path` | `:path-params` | +| type | request source | +|--------------|--------------------------------------------------| +| `:query` | `:query-params` | +| `:body` | `:body-params` | +| `:request` | `:body-params`, allows per-content-type coercion | +| `:form` | `:form-params` | +| `:header` | `:header-params` | +| `:path` | `:path-params` | +| `:multipart` | `:multipart-params`, only properly supports `clojure.spec`, see [Default Middleware](default_middleware.md) | To enable coercion, the following things need to be done: From b5c9ee274d41842c22b4c101bd2505bfc1ce588f Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Thu, 16 Mar 2023 09:53:38 +0200 Subject: [PATCH 43/50] feat: generate correct openapi for reitit.ring.malli --- modules/reitit-malli/src/reitit/ring/malli.cljc | 6 ++++-- test/cljc/reitit/openapi_test.clj | 7 ++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/modules/reitit-malli/src/reitit/ring/malli.cljc b/modules/reitit-malli/src/reitit/ring/malli.cljc index a7987625..1ccc1959 100644 --- a/modules/reitit-malli/src/reitit/ring/malli.cljc +++ b/modules/reitit-malli/src/reitit/ring/malli.cljc @@ -4,7 +4,8 @@ #?(:clj (def temp-file-part "Schema for file param created by ring.middleware.multipart-params.temp-file store." - [:map {:json-schema {:type "file"}} + [:map {:json-schema {:type "string" + :format "binary"}} [:filename string?] [:content-type string?] [:size int?] @@ -13,7 +14,8 @@ #?(:clj (def bytes-part "Schema for file param created by ring.middleware.multipart-params.byte-array store." - [:map {:json-schema {:type "file"}} + [:map {:json-schema {:type "string" + :format "binary"}} [:filename string?] [:content-type string?] [:bytes bytes?]])) diff --git a/test/cljc/reitit/openapi_test.clj b/test/cljc/reitit/openapi_test.clj index 7e9bfcda..563125ed 100644 --- a/test/cljc/reitit/openapi_test.clj +++ b/test/cljc/reitit/openapi_test.clj @@ -11,6 +11,7 @@ [reitit.http.interceptors.multipart] [reitit.openapi :as openapi] [reitit.ring :as ring] + [reitit.ring.malli] [reitit.ring.spec] [reitit.ring.coercion :as rrc] [reitit.swagger-ui :as swagger-ui] @@ -434,11 +435,7 @@ (deftest multipart-test (doseq [[coercion file-schema string-schema] [[#'malli/coercion - [:map {:json-schema {:type "string" - :format "binary"}} - [:filename :string] - [:content-type :string] - [:bytes :int]] + reitit.ring.malli/bytes-part :string] [#'schema/coercion (schema-tools.core/schema {:filename s/Str From 224acf930e79f915905c9a00e9f5380b8319c233 Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Thu, 16 Mar 2023 10:05:23 +0200 Subject: [PATCH 44/50] doc: openapi3 in examples/ring-malli-swagger --- examples/ring-malli-swagger/README.md | 6 +++++- examples/ring-malli-swagger/project.clj | 3 ++- .../ring-malli-swagger/src/example/server.clj | 18 ++++++++++++++++-- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/examples/ring-malli-swagger/README.md b/examples/ring-malli-swagger/README.md index 3a2144c2..6162591b 100644 --- a/examples/ring-malli-swagger/README.md +++ b/examples/ring-malli-swagger/README.md @@ -7,6 +7,10 @@ (start) ``` +- Swagger spec served at +- Openapi spec served at +- Swagger UI served at + To test the endpoints using [httpie](https://httpie.org/): ```bash @@ -20,4 +24,4 @@ http GET :3000/swagger.json ## License -Copyright © 2017-2019 Metosin Oy +Copyright © 2017-2023 Metosin Oy diff --git a/examples/ring-malli-swagger/project.clj b/examples/ring-malli-swagger/project.clj index 406e05c6..a2991dae 100644 --- a/examples/ring-malli-swagger/project.clj +++ b/examples/ring-malli-swagger/project.clj @@ -3,6 +3,7 @@ :dependencies [[org.clojure/clojure "1.10.0"] [metosin/jsonista "0.2.6"] [ring/ring-jetty-adapter "1.7.1"] - [metosin/reitit "0.6.0"]] + [metosin/reitit "0.6.0"] + [metosin/ring-swagger-ui "5.0.0-alpha.0"]] :repl-options {:init-ns example.server} :profiles {:dev {:dependencies [[ring/ring-mock "0.3.2"]]}}) diff --git a/examples/ring-malli-swagger/src/example/server.clj b/examples/ring-malli-swagger/src/example/server.clj index bc9c3861..1b3d880d 100644 --- a/examples/ring-malli-swagger/src/example/server.clj +++ b/examples/ring-malli-swagger/src/example/server.clj @@ -1,6 +1,7 @@ (ns example.server (:require [reitit.ring :as ring] [reitit.coercion.malli] + [reitit.openapi :as openapi] [reitit.ring.malli] [reitit.swagger :as swagger] [reitit.swagger-ui :as swagger-ui] @@ -24,10 +25,17 @@ [["/swagger.json" {:get {:no-doc true :swagger {:info {:title "my-api" - :description "with [malli](https://github.com/metosin/malli) and reitit-ring"} + :description "swagger docs with [malli](https://github.com/metosin/malli) and reitit-ring" + :version "0.0.1"} :tags [{:name "files", :description "file api"} {:name "math", :description "math api"}]} :handler (swagger/create-swagger-handler)}}] + ["/openapi.json" + {:get {:no-doc true + :openapi {:info {:title "my-api" + :description "openapi3 docs with [malli](https://github.com/metosin/malli) and reitit-ring" + :version "0.0.1"}} + :handler (openapi/create-openapi-handler)}}] ["/files" {:swagger {:tags ["files"]}} @@ -44,6 +52,8 @@ ["/download" {:get {:summary "downloads a file" :swagger {:produces ["image/png"]} + :responses {200 {:description "an image" + :content {"image/png" any?}}} :handler (fn [_] {:status 200 :headers {"Content-Type" "image/png"} @@ -96,8 +106,9 @@ ;; malli options :options nil}) :muuntaja m/instance - :middleware [;; swagger feature + :middleware [;; swagger & openapi swagger/swagger-feature + openapi/openapi-feature ;; query-params & form-params parameters/parameters-middleware ;; content-negotiation @@ -118,6 +129,9 @@ (swagger-ui/create-swagger-ui-handler {:path "/" :config {:validatorUrl nil + :urls [{:name "swagger", :url "swagger.json"} + {:name "openapi", :url "openapi.json"}] + :urls.primaryName "openapi" :operationsSorter "alpha"}}) (ring/create-default-handler)))) From 6c3db021636c43da8499361785b6dca682927c1d Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Thu, 16 Mar 2023 10:08:06 +0200 Subject: [PATCH 45/50] doc: openapi content type for file download in examples/http-swagger --- examples/http-swagger/src/example/server.clj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/http-swagger/src/example/server.clj b/examples/http-swagger/src/example/server.clj index c7251d9c..d0a9164e 100644 --- a/examples/http-swagger/src/example/server.clj +++ b/examples/http-swagger/src/example/server.clj @@ -76,6 +76,8 @@ ["/download" {:get {:summary "downloads a file" :swagger {:produces ["image/png"]} + :responses {200 {:description "an image" + :content {"image/png" any?}}} :handler (fn [_] {:status 200 :headers {"Content-Type" "image/png"} From bf8d0ba1ef6a116cf5ca271083951dc8ec58439a Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Thu, 16 Mar 2023 10:18:14 +0200 Subject: [PATCH 46/50] doc: don't say :multipart only works with spec --- doc/ring/coercion.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ring/coercion.md b/doc/ring/coercion.md index 8de57c1a..8ca8fb68 100644 --- a/doc/ring/coercion.md +++ b/doc/ring/coercion.md @@ -12,7 +12,7 @@ The following request parameters are currently supported: | `:form` | `:form-params` | | `:header` | `:header-params` | | `:path` | `:path-params` | -| `:multipart` | `:multipart-params`, only properly supports `clojure.spec`, see [Default Middleware](default_middleware.md) | +| `:multipart` | `:multipart-params`, see [Default Middleware](default_middleware.md) | To enable coercion, the following things need to be done: From 9a99ed96b2def8058904038ceed7158fb18a5eb8 Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Thu, 16 Mar 2023 10:19:30 +0200 Subject: [PATCH 47/50] doc: link to examples/ring-malli-swagger from doc/ring/openapi.md --- doc/ring/openapi.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/ring/openapi.md b/doc/ring/openapi.md index 3a1f5fcf..d3e127cf 100644 --- a/doc/ring/openapi.md +++ b/doc/ring/openapi.md @@ -5,7 +5,9 @@ Reitit can generate [OpenAPI 3.1.0](https://spec.openapis.org/oas/v3.1.0) documentation. The feature works similarly to [Swagger documentation](swagger.md). -The [http-swagger example](../../examples/http-swagger) also has OpenAPI documentation. +The [http-swagger](../../examples/http-swagger) and +[ring-malli-swagger](../../examples/ring-malli-swagger) examples also +have OpenAPI documentation. ## OpenAPI data From 389f4a29da3b4b7c196ea1ddb5ac7668a2e8e98e Mon Sep 17 00:00:00 2001 From: Tommi Reiman Date: Fri, 17 Mar 2023 10:12:15 +0200 Subject: [PATCH 48/50] format --- examples/ring-malli-swagger/src/example/server.clj | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/ring-malli-swagger/src/example/server.clj b/examples/ring-malli-swagger/src/example/server.clj index 1b3d880d..a213edaa 100644 --- a/examples/ring-malli-swagger/src/example/server.clj +++ b/examples/ring-malli-swagger/src/example/server.clj @@ -31,11 +31,11 @@ {:name "math", :description "math api"}]} :handler (swagger/create-swagger-handler)}}] ["/openapi.json" - {:get {:no-doc true - :openapi {:info {:title "my-api" - :description "openapi3 docs with [malli](https://github.com/metosin/malli) and reitit-ring" - :version "0.0.1"}} - :handler (openapi/create-openapi-handler)}}] + {:get {:no-doc true + :openapi {:info {:title "my-api" + :description "openapi3 docs with [malli](https://github.com/metosin/malli) and reitit-ring" + :version "0.0.1"}} + :handler (openapi/create-openapi-handler)}}] ["/files" {:swagger {:tags ["files"]}} From d8e28e153b1733986708895d2bc15fbc0de20c93 Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Fri, 17 Mar 2023 14:43:49 +0200 Subject: [PATCH 49/50] fix: swagger multipart support 1. For spec we were including some extra stuff in the parameter specification: {:description "", :in "formData", :name "file", :properties {"bytes" {:format "byte", :type "string"}, "content-type" {:type "string"}, "filename" {:type "string"}}, :required ["filename" "content-type" "bytes"], :type "file"} 2. For malli the :type changed from "file" to "string" because of openapi changes. Now openapi and swagger both get the right type. 3. Test for swagger multipart support --- .../reitit/http/interceptors/multipart.clj | 4 +- .../reitit-malli/src/reitit/ring/malli.cljc | 6 ++- test/cljc/reitit/swagger_test.clj | 49 +++++++++++++++++++ 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/modules/reitit-interceptors/src/reitit/http/interceptors/multipart.clj b/modules/reitit-interceptors/src/reitit/http/interceptors/multipart.clj index 4a2d4ba1..b4bd02e5 100644 --- a/modules/reitit-interceptors/src/reitit/http/interceptors/multipart.clj +++ b/modules/reitit-interceptors/src/reitit/http/interceptors/multipart.clj @@ -19,7 +19,7 @@ "Spec for file param created by ring.middleware.multipart-params.temp-file store." (st/spec {:spec (s/keys :req-un [::filename ::content-type ::tempfile ::size]) - :swagger/type "file" + :swagger {:type "file"} :openapi {:type "string" :format "binary"}})) @@ -27,7 +27,7 @@ "Spec for file param created by ring.middleware.multipart-params.byte-array store." (st/spec {:spec (s/keys :req-un [::filename ::content-type ::bytes]) - :swagger/type "file" + :swagger {:type "file"} :openapi {:type "string" :format "binary"}})) diff --git a/modules/reitit-malli/src/reitit/ring/malli.cljc b/modules/reitit-malli/src/reitit/ring/malli.cljc index 1ccc1959..9815fef5 100644 --- a/modules/reitit-malli/src/reitit/ring/malli.cljc +++ b/modules/reitit-malli/src/reitit/ring/malli.cljc @@ -4,7 +4,8 @@ #?(:clj (def temp-file-part "Schema for file param created by ring.middleware.multipart-params.temp-file store." - [:map {:json-schema {:type "string" + [:map {:swagger {:type "file"} + :json-schema {:type "string" :format "binary"}} [:filename string?] [:content-type string?] @@ -14,7 +15,8 @@ #?(:clj (def bytes-part "Schema for file param created by ring.middleware.multipart-params.byte-array store." - [:map {:json-schema {:type "string" + [:map {:swagger {:type "file"} + :json-schema {:type "string" :format "binary"}} [:filename string?] [:content-type string?] diff --git a/test/cljc/reitit/swagger_test.clj b/test/cljc/reitit/swagger_test.clj index 542b7ebd..d2d18c31 100644 --- a/test/cljc/reitit/swagger_test.clj +++ b/test/cljc/reitit/swagger_test.clj @@ -1,16 +1,27 @@ (ns reitit.swagger-test (:require [clojure.test :refer [deftest is testing]] + [jsonista.core :as j] [muuntaja.core :as m] [reitit.coercion.malli :as malli] [reitit.coercion.schema :as schema] [reitit.coercion.spec :as spec] + [reitit.http.interceptors.multipart] [reitit.ring :as ring] + [reitit.ring.malli] [reitit.ring.coercion :as rrc] [reitit.swagger :as swagger] [reitit.swagger-ui :as swagger-ui] [schema.core :as s] [spec-tools.data-spec :as ds])) +(defn- normalize + "Normalize format of swagger spec by converting it to json and back. + Handles differences like :q vs \"q\" in swagger generation." + [data] + (-> data + j/write-value-as-string + (j/read-value j/keyword-keys-object-mapper))) + (def app (ring/ring-handler (ring/router @@ -410,3 +421,41 @@ :handler (swagger/create-swagger-handler)}}]])) output (with-out-str (app {:request-method :get, :uri "/swagger.json"}))] (is (.contains output "WARN"))))) + +(deftest multipart-test + (doseq [[coercion file-schema string-schema] + [[#'malli/coercion + reitit.ring.malli/bytes-part + :string] + [#'spec/coercion + reitit.http.interceptors.multipart/bytes-part + string?]]] + (testing coercion + (let [app (ring/ring-handler + (ring/router + [["/upload" + {:post {:decription "upload" + :coercion @coercion + :parameters {:multipart {:file file-schema + :more string-schema}} + :handler identity}}] + ["/swagger.json" + {:get {:no-doc true + :handler (swagger/create-swagger-handler)}}]] + {:data {:middleware [swagger/swagger-feature]}})) + spec (-> {:request-method :get + :uri "/swagger.json"} + app + :body)] + (is (= [{:description "" + :in "formData" + :name "file" + :required true + :type "file"} + {:description "" + :in "formData" + :name "more" + :required true + :type "string"}] + (normalize + (get-in spec [:paths "/upload" :post :parameters])))))))) From 8272b651e37d6bdbda471f4e2da9ff3f3427974a Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Fri, 17 Mar 2023 14:58:28 +0200 Subject: [PATCH 50/50] doc: examples/ring-malli-swagger: share tags between swagger&openapi --- examples/ring-malli-swagger/src/example/server.clj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/ring-malli-swagger/src/example/server.clj b/examples/ring-malli-swagger/src/example/server.clj index a213edaa..b7ab91b4 100644 --- a/examples/ring-malli-swagger/src/example/server.clj +++ b/examples/ring-malli-swagger/src/example/server.clj @@ -38,7 +38,7 @@ :handler (openapi/create-openapi-handler)}}] ["/files" - {:swagger {:tags ["files"]}} + {:tags ["files"]} ["/upload" {:post {:summary "upload a file" @@ -62,7 +62,7 @@ (io/input-stream))})}}]] ["/math" - {:swagger {:tags ["math"]}} + {:tags ["math"]} ["/plus" {:get {:summary "plus with malli query parameters"