From e96e13c81abb5e803541939d618305c21666da76 Mon Sep 17 00:00:00 2001 From: John Shaffer Date: Fri, 26 Apr 2024 01:50:33 -0500 Subject: [PATCH] Support OpenAPI 3.1.0 JsonSchemas (#5) --- deps.edn | 2 +- src/navi/core.clj | 63 ++++++++++++++++++++++--------------- test/navi/core_test.clj | 70 +++++++++++++++++++++++++++++++++++------ 3 files changed, 98 insertions(+), 37 deletions(-) diff --git a/deps.edn b/deps.edn index e796f8b..4af5090 100644 --- a/deps.edn +++ b/deps.edn @@ -4,7 +4,7 @@ ; license that can be found in the LICENSE file or at ; https://opensource.org/licenses/MIT. -{:deps {io.swagger.parser.v3/swagger-parser {:mvn/version "2.1.18"}} +{:deps {io.swagger.parser.v3/swagger-parser {:mvn/version "2.1.22"}} :aliases {:test {:extra-deps {lambdaisland/kaocha {:mvn/version "1.87.1366"} org.clojure/test.check {:mvn/version "1.1.1"}} :main-opts ["-m" "kaocha.runner" "--reporter" "kaocha.report.progress/report"]}}} diff --git a/src/navi/core.clj b/src/navi/core.clj index 6cccc04..ffdeeb1 100644 --- a/src/navi/core.clj +++ b/src/navi/core.clj @@ -6,14 +6,7 @@ (ns navi.core (:import [java.util Map$Entry] - [io.swagger.v3.oas.models.media StringSchema - IntegerSchema - ObjectSchema - ArraySchema - NumberSchema - BooleanSchema - UUIDSchema - MediaType] + [io.swagger.v3.oas.models.media MediaType Schema] [io.swagger.v3.oas.models.parameters PathParameter HeaderParameter QueryParameter @@ -34,6 +27,19 @@ (contains? m k) (update-in [k] #(into [:map] %)))) +(defn schema->spec [^Schema schema] + (let [types (.getTypes schema)] + (if (= 1 (count types)) + (spec schema) + (try + (->> (map (fn [type] + (.setTypes schema #{type}) + (spec schema)) + types) + (into [:or])) + (finally + (.setTypes schema types)))))) + ;; TODO: Better (defn ->prop-schema "Given a property and a required keys set, returns a malli spec. @@ -47,7 +53,7 @@ (conj key-schema (-> property .getValue - spec)))) + schema->spec)))) (defn ->param-schema "Given a param applies the similar logic as prop to schema @@ -62,38 +68,43 @@ (conj key-spec (-> param .getSchema - spec)))) + schema->spec)))) -(defmulti spec class) +(defmulti spec + (fn [^Schema schema] + (first (.getTypes schema)))) (defmethod spec - StringSchema - [_] - string?) + "string" + [^Schema schema] + (if (= "uuid" (.getFormat schema)) + uuid? + string?)) (defmethod spec - IntegerSchema + "integer" [_] int?) -(defmethod spec - NumberSchema +(defmethod spec + "number" [_] number?) (defmethod spec - BooleanSchema + "boolean" [_] boolean?) +; Added in OpenAPI 3.1.0 (defmethod spec - UUIDSchema + "null" [_] - uuid?) + nil?) (defmethod spec - ObjectSchema - [^ObjectSchema schema] + "object" + [^Schema schema] (let [required (->> schema .getRequired (into #{})) @@ -104,13 +115,13 @@ (into [:map {:closed false}] schemas))) (defmethod spec - ArraySchema - [^ArraySchema schema] + "array" + [^Schema schema] (let [items (.getItems schema)] [:sequential (if (nil? items) any? - (spec items))])) + (schema->spec items))])) (defmulti param->data class) @@ -142,7 +153,7 @@ .get) body-spec (-> content .getSchema - spec)] + schema->spec)] {:body (if (.getRequired param) body-spec [:or nil? body-spec])})) diff --git a/test/navi/core_test.clj b/test/navi/core_test.clj index f439913..3d134b0 100644 --- a/test/navi/core_test.clj +++ b/test/navi/core_test.clj @@ -9,7 +9,8 @@ [navi.core :as core]) (:import [java.util Map LinkedHashMap] [io.swagger.v3.oas.models Operation PathItem] - [io.swagger.v3.oas.models.media Content StringSchema IntegerSchema ObjectSchema ArraySchema MediaType UUIDSchema] + [io.swagger.v3.oas.models.media Content StringSchema IntegerSchema JsonSchema + NumberSchema ObjectSchema ArraySchema MediaType UUIDSchema Schema] [io.swagger.v3.oas.models.parameters Parameter PathParameter HeaderParameter QueryParameter RequestBody])) (deftest map-to-malli-spec @@ -48,33 +49,82 @@ (deftest openapi-schema-to-malli-spec (testing "string" (is (= string? - (core/spec (StringSchema.))))) + (core/schema->spec (StringSchema.)))) + (is (= string? + (core/schema->spec (doto (Schema.) + (.addType "string")))))) (testing "integer" (is (= int? - (core/spec (IntegerSchema.))))) + (core/schema->spec (IntegerSchema.)))) + (is (= int? + (core/schema->spec (doto (Schema.) + (.addType "integer")))))) + (testing "number" + (is (= number? + (core/schema->spec (NumberSchema.)))) + (is (= number? + (core/schema->spec (doto (Schema.) + (.addType "number")))))) + (testing "null" + (is (= nil? + (core/schema->spec (doto (Schema.) + (.addType "null")))))) (testing "empty object" (is (= [:map {:closed false}] - (core/spec (ObjectSchema.))))) + (core/schema->spec (ObjectSchema.)))) + (is (= [:map {:closed false}] + (core/schema->spec (doto (Schema.) + (.addType "object")))))) (testing "object" (let [props (doto (LinkedHashMap.) (.put "x" (IntegerSchema.)) (.put "y" (StringSchema.))) obj (doto (ObjectSchema.) (.setRequired ["y" "x"]) - (.setProperties props))] + (.setProperties props)) + props-json (doto (LinkedHashMap.) + (.put "x" (doto (Schema.) + (.addType "integer"))) + (.put "y" (doto (Schema.) + (.addType "string")))) + obj-json (doto (Schema.) + (.addType "object") + (.setRequired ["y" "x"]) + (.setProperties props-json))] + (is (= [:map {:closed false} [:x int?] [:y string?]] + (core/schema->spec obj))) (is (= [:map {:closed false} [:x int?] [:y string?]] - (core/spec obj))))) + (core/schema->spec obj-json))))) (testing "empty array" (is (= [:sequential any?] - (core/spec (ArraySchema.))))) + (core/schema->spec (ArraySchema.)))) + (is (= [:sequential any?] + (core/schema->spec (doto (Schema.) + (.addType "array")))))) (testing "array" (let [arr (doto (ArraySchema.) - (.setItems (StringSchema.)))] + (.setItems (StringSchema.))) + arr-json (doto (Schema.) + (.addType "array") + (.setItems (doto (Schema.) + (.addType "string"))))] + (is (= [:sequential string?] + (core/schema->spec arr))) (is (= [:sequential string?] - (core/spec arr))))) + (core/schema->spec arr-json))))) (testing "uuid" (is (= uuid? - (core/spec (UUIDSchema.)))))) + (core/schema->spec (UUIDSchema.)))) + (is (= uuid? + (core/schema->spec (doto (Schema.) + (.addType "string") + (.setFormat "uuid")))))) + + (testing "jsonschemas with multiple types" + (let [strint (-> (JsonSchema.) + (.types #{"string" "integer"}))] + (is (#{[:or string? int?] [:or int? string?]} + (core/schema->spec strint)))))) (deftest parameters-to-malli-spec (testing "path"