(require '[spec-tools.swagger.core :as swagger])
An utility to transform Specs to Swagger2 Schemas.
swagger/transform
converts specs into Swagger2 Schema. Transformation can be customized with the following optional options:
:type
- a target type, either:parameter
(Parameter Object) or:schema
(Schema Object). If value is not defined,:schema
is used.:in
- a parameter subtype, which is one of::query
,:header
,:path
,:body
or:formData
. See Parameter Object for details.
NOTE: As clojure.spec
is more powerful than the Swagger2 Schema, we are losing some data in the transformation. We try to retain all the information using vendor extensions.
(swagger/transform float?)
; {:type "number" :format "float"}
;; no "null" in swagger2
(swagger/transform (s/nilable string?))
; {:type "string", :x-nullable true}
;; swagger2 parameter syntax
(swagger/transform (s/nilable string?) {:type :parameter})
; {:type "string", :allowEmptyValue true}
;; no "anyOf" in swagger2
(swagger/transform (s/cat :int integer? :string string?))
; {:type "array"
; :items {:type "integer"
; :x-anyOf [{:type "integer"}
; {:type "string"}]}}
Specs can be annotated to populate the JSON Schemas.
:name
is copied into:title
:description
is used as-is- all keys with namespace
json-schema
andswagger
are used without the namespace
(require '[spec-tools.core :as st])
(swagger/transform
(st/spec
{:spec integer?
:name "integer"
:description "it's an int"
:swagger/example 42
:json-schema/default 42}))
;{:type "integer"
; :title "integer"
; :description "it's an int"
; :example 42
; :default 42}
swagger/swagger-spec
function takes an extended swagger2 spec as map and transforms it into a valid Swagger Object. Rules:
- by default, data is passed through, allowing any valid swagger data to be used
- for qualified map keys,
swagger/expand
multimethod is invoked with the key, value and the map as arguments- dispatches on the key, defaulting to
::swagger/extension
- returns a map that get's merged in to original map, without the dispatched key
- dispatches on the key, defaulting to
Predefined dispatch keys below.
Value should be a map with optional keys :body
, :query
, :path
, :header
and :formData
. For all but :body
, the value should be a s/keys
spec (describing the ring parameters). With :body
, the value can be any clojure.spec.alpha/Spec
.
Returns a map with key :parameters
with value of vector of swagger Parameter Objects, merged over the existing :parameters
. Duplicate parameters (with identical :in
and :name
are overridden)
(require '[clojure.spec.alpha :as s])
(s/def ::id string?)
(s/def ::name string?)
(s/def ::street string?)
(s/def ::city #{:tre :hki})
(s/def ::address (s/keys :req-un [::street ::city]))
(s/def ::user (s/keys :req-un [::id ::name ::address]))
(swagger/swagger-spec
{:paths
{"echo"
{:post
{:parameters
[;; existing parameter, will be overriddden
{:in "query"
:name "name"
:required false}
;; unique parameter, will remain
{:in "query"
:name "name2"
:type "string"
:required true}]
;; the spec-parameters
::swagger/parameters
{:query (s/keys :opt-un [::name])
:body ::user}}}}})
;{:paths
; {"echo"
; {:post
; {:parameters [{:in "query"
; :name "name2"
; :type "string"
; :required true}
; {:in "query"
; :name "name"
; :description ""
; :type "string"
; :required false}
; {:in "body",
; :name "user/user",
; :description "",
; :required true,
; :schema {:type "object",
; :properties {"id" {:type "string"},
; "name" {:type "string"},
; "address" {:type "object",
; :properties {"street" {:type "string"},
; "city" {:enum [:tre :hki],
; :type "string"}},
; :required ["street" "city"],
; :title "user/address"}},
; :required ["id" "name" "address"],
; :title "user/user"}}]}}}}
Value should a Swagger2 Responses Definition Object with Spec or Spec as the :schema
. Returns a map with key :responses
with :schemas
transformed into Swagger2 Schema Objects, merged over existing :responses
.
(swagger/swagger-spec
{:responses {404 {:description "fail"}
500 {:description "fail"}}
::swagger/responses {200 {:schema ::user
:description "Found it!"}
404 {:description "Ohnoes."}}})
;{:responses {404 {:description "Ohnoes."}
; 500 {:description "fail"}
; 200 {:schema {:type "object"
; :properties {"id" {:type "string"}
; "name" {:type "string"}
; "address" {:type "object"
; :properties {"street" {:type "string"}
; "city" {:type "string"
; :enum [:tre :hki]}}
; :required ["street" "city"]
; :title "user/address"}}
; :required ["id" "name" "address"]
; :title "user/user"}
; :description "Found it!"}}}
(require '[spec-tools.swagger.core :as swagger])
(require '[clojure.spec.alpha :as s])
(s/def ::id string?)
(s/def ::name string?)
(s/def ::street string?)
(s/def ::city #{:tre :hki})
(s/def ::address (s/keys :req-un [::street ::city]))
(s/def ::user (s/keys :req-un [::id ::name ::address]))
(swagger/swagger-spec
{:swagger "2.0"
:info {:version "1.0.0"
:title "Sausages"
:description "Sausage description"
:termsOfService "http://helloreverb.com/terms/"
:contact {:name "My API Team"
:email "[email protected]"
:url "http://www.metosin.fi"}
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}}
:tags [{:name "user"
:description "User stuff"}]
:paths {"/api/ping" {:get {:responses {:default {:description ""}}}}
"/user/:id" {:post {:summary "User Api"
:description "User Api description"
:tags ["user"]
::swagger/parameters {:path (s/keys :req [::id])
:body ::user}
::swagger/responses {200 {:schema ::user
:description "Found it!"}
404 {:description "Ohnoes."}}}}}})
;{:swagger "2.0"
; :info {:version "1.0.0"
; :title "Sausages"
; :description "Sausage description"
; :termsOfService "http://helloreverb.com/terms/"
; :contact {:name "My API Team" :email "[email protected]" :url "http://www.metosin.fi"}
; :license {:name "Eclipse Public License" :url "http://www.eclipse.org/legal/epl-v10.html"}}
; :tags [{:name "user" :description "User stuff"}]
; :paths {"/api/ping" {:get {:responses {:default {:description ""}}}}
; "/user/:id" {:post
; {:summary "User Api"
; :description "User Api description"
; :tags ["user"]
; :parameters [{:in "path"
; :name "user/id"
; :description ""
; :type "string"
; :required true}
; {:in "body"
; :name "user/user"
; :description ""
; :required true
; :schema {:type "object"
; :properties {"id" {:type "string"}
; "name" {:type "string"}
; "address" {:type "object"
; :properties {"street" {:type "string"}
; "city" {:type "string"
; :enum [:tre :hki]}}
; :required ["street" "city"]
; :title "user/address"}}
; :required ["id" "name" "address"]
; :title "user/user"}}]
; :responses {200 {:schema {:type "object"
; :properties {"id" {:type "string"}
; "name" {:type "string"}
; "address" {:type "object"
; :properties {"street" {:type "string"}
; "city" {:enum [:tre :hki]
; :type "string"}}
; :required ["street" "city"]
; :title "user/address"}}
; :required ["id" "name" "address"]
; :title "user/user"}
; :description "Found it!"}
; 404 {:description "Ohnoes."}}}}}}