Swagger implementation for Clojure/Ring using Prismatic Schema for data modeling.
- API Docs
- Supports both 1.2 and 2.0 Swagger Specs
- For web developers
- Extendable Schema->JSON Mappings with out-of-the-box support for most common types
- Utilities for input & output Schema validation & coercion
- For web library developers:
- A Schema-based contract for collecting route documentation from the web apps
- Extendable Schema->JSON Schema conversion with out-of-the-box support for most Schema predicates
- Common middleware for handling Schemas and Validation Errors.
- Ring-handlers for exposing the swaggers artifacts
- swagger.json for 2.0.
- Resource listing and Api declarations for 1.2.
- Swagger-UI bindings. (the UI itself is jar-packaged separately or you can get it from NPM)
- Compojure-Api for Compojure
- fnhouse-swagger for fnhouse
- pedastal-swagger for Pedastal
- yada
Route definitions as expected as a clojure Map defined by the Schema. The Schema is open as ring-swagger tries not to be on your way - one can always pass any extra data in the Swagger Spec format.
(require '[ring.swagger.swagger2 :as rs])
(rs/swagger-json nil)
; {:swagger "2.0"
; :info {:title "Swagger API"
; :version "0.0.1"}
; :produces ["application/json"]
; :consumes ["application/json"]
; :definitions {}
; :paths {}}... with info, tags, routes and anonymous nested schemas.
(require '[schema.core :as s])
(s/defschema User {:id s/Str,
:name s/Str
:address {:street s/Str
:city (s/enum :tre :hki)}})
(s/with-fn-validation
(rs/swagger-json
{: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 nil}
"/user/:id" {:post {:summary "User Api"
:description "User Api description"
:tags ["user"]
:parameters {:path {:id s/Str}
:body User}
: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 {:email "[email protected]"
; :name "My API Team"
; :url "http://www.metosin.fi"}
; :license {:name "Eclipse Public License"
; :url "http://www.eclipse.org/legal/epl-v10.html"}}
; :tags [{:description "User stuff" :name "user"}]
; :consumes ["application/json"]
; :produces ["application/json"]
; :definitions {"User" {:type "object"
; :properties {:address {:$ref "#/definitions/UserAddress"}
; :id {:type "string"}
; :name {:type "string"}}
; :required (:id :name :address)}
; "UserAddress" {:type "object"
; :properties {:city {:enum (:tre :hki)
; :type "string"}
; :street {:type "string"}}
; :required (:street :city)}}
; :paths {"/api/ping" {:get {:responses {:default {:description ""}}}}
; "/user/{id}" {:post {:description "User Api description"
; :summary "User Api"
; :tags ["user"]
; :parameters [{:description ""
; :in :path
; :name "id"
; :required true
; :type "string"}
; {:description ""
; :in :body
; :name "User"
; :required true
; :schema {:$ref "#/definitions/User"}}]
; :responses {200 {:description "Found it!"
; :schema {:$ref "#/definitions/User"}}
; 404 {:description "Ohnoes."}}}}}}One can pass extra options-map as a third parameter to swagger-json. The following options are available:
:ignore-missing-mappings? - (false) boolean whether to silently ignore
missing schema to JSON Schema mappings. if
set to false, IllegalArgumentException is
thrown if a Schema can't be presented as
JSON Schema.
:default-response-description-fn - ((constantly "")) - a fn to generate default
response descriptions from http status code.
Takes a status code (Int) and returns a String.
:handle-duplicate-schemas-fn - (ring.swagger.core/ignore-duplicate-schemas),
a function to handle possible duplicate schema
definitions. Takes schema-name and set of found
attached schema values as parameters. Returns
sequence of schema-name and selected schema value.For example, to get default response descriptions from the HTTP Spec, you can do the following:
(require '[ring.util.http-status :as status])
(rs/swagger-json
{:paths {"/hello" {:post {:responses {200 nil
425 nil
500 {:description "FAIL"}}}}}}
{:default-response-description-fn status/get-description})
; {:swagger "2.0"
; :info {:title "Swagger API" :version "0.0.1"}
; :consumes ["application/json"]
; :produces ["application/json"]
; :definitions {}
; :paths {"/hello" {:post {:responses {200 {:description "OK"}
; 425 {:description "The collection is unordered."}
; 500 {:description "FAIL"}}}}}}The generated full spec can be validated against the Swagger JSON Schema via tools like scjsv.
(require '[scjsv.core :as scjsv])
(def validator (scjsv/validator (slurp "https://raw.githubusercontent.com/reverb/swagger-spec/master/schemas/v2.0/schema.json")))
(validator (rs/swagger-json {:paths {"/api/ping" {:get nil}}}))
; nil
(validator (rs/swagger-json {:pathz {"/api/ping" {:get nil}}}))
; ({:level "error"
; :schema {:loadingURI "#", :pointer ""}
; :instance {:pointer ""}
; :domain "validation"
; :keyword "additionalProperties"
; :message "object instance has properties which are not allowed by the schema: [\"pathz\"]", :unwanted ["pathz"]})For more information about creating your own adapter, see Collecting API Documentation.
Prismatic Schema is used for modeling both the input & output schemas for routes.
As Swagger 2.0 Spec Schema is a pragmatic and deterministic subset of JSON Schema, so not all Clojure Schema elements can be used.
| Clojure Schema | JSON Schema | Sample JSON |
|---|---|---|
Integer |
integer, int32 | 1 |
Long, s/Int |
integer, int64 | 1 |
Double, Number, s/Num |
number, double | 1.2 |
String, s/Str, Keyword, s/Keyword |
string | "kikka" |
Boolean |
boolean | true |
nil, s/Any |
void | |
java.util.Date, org.joda.time.DateTime |
string, date-time | "2014-02-18T18:25:37.456Z", consumes also without millis: "2014-02-18T18:25:37Z" |
java.util.regex.Pattern, |
string, regex | [a-z0-9] |
#"[a-z0-9]+" |
string, pattern | "a6" |
s/Uuid, java.util.UUID |
string, uuid | "77e70512-1337-dead-beef-0123456789ab" |
org.joda.time.LocalDate |
string, date | "2014-02-19" |
(s/enum X Y Z) |
type of X, enum(X,Y,Z) | |
(s/maybe X) |
type of X | |
(s/both X Y Z) |
type of X | |
(s/either X Y Z) |
type of X | |
(s/named X name) |
type of X | |
(s/one X name) |
type of X | |
(s/recursive Var) |
Ref to (model) Var | |
(s/eq X) |
type of class of X | |
(s/optional-key X) |
optional key | |
(s/required-key X) |
required key | |
s/Keyword (as a key) |
ignored |
- All supported types have symmetric JSON serialization (Cheshire encoders) & deserialization (Schema coercions)
- Vectors, Sets and Maps can be used as containers
- Maps are presented as Complex Types and References. Model references are resolved automatically.
- Nested maps are transformed automatically into flat maps with generated child references
- Nested maps can be within valid containers (as only element - heterogeneous schema sequences not supported by the spec)
If ring-swagger can't transform the Schemas into JSON Schemas, by default a IllegalArgumentException will be thrown. Binding ring.swagger.json-schema/*ignore-missing-mappings* to true, one
can ignore the errors (missing schema elements will be ignored from
the generated JSON Schema).
Prismatic Schema names are used to name the Swagger Body & Response models. Nested schemas are traversed and all found sub-schemas are generated automatically a name (so that they can be referenced in the JSON Schema).
If multiple such schemas have same name but have different value, an describive IllegalArgumentException is raised. This can happen if one transforms schemas via normal clojure.core functions:
(s/defschema User {:id s/Str, :name s/Str})
(def NewUser (dissoc User :id))
(meta User)
; {:name Kikka}
(meta NewUser)
; {:name Kikka} <- fail!There are better schema transformers functions available at schema-tools.
JSON Schema generation is supported by the ring.swagger.json-schema/json-type multimethod. One can register own schema types by installing new methods for it's dispatch:
(require '[ring.swagger.json-schema :as jsons])
(require '[schema.core :as s])
(defmethod jsons/json-type s/Maybe [e] (swagger/->json (:schema e)))One might also need to write both JSON Serialization for the Schema values and coercion function to de-serialize the value back from JSON.
Some Schema elements are impossible to accurately describe within boundaries of JSON-Schema or Swagger spec.
You can require ring.swagger.json-schema-dirty namespace to get implementations for json-type multimethod which allow
you to use some of these elements.
Be warned that Swagger-UI might not display these correctly and the code generated by swagger-codegen will be inaccurate.
| Clojure | JSON Schema | Sample |
|---|---|---|
(s/conditional pred X pred Y pred Z) |
oneOf: type of X, type of X, type of Z | |
(s/if pred X Y) |
oneOf: type of X, type of Y |
These schemas should work, just need the mappings (feel free to contribute!):
s/Symbols/Inst
Ring-swagger utilizes Schema coercions for transforming the input data into vanilla Clojure and back.
(require '[schema.core :as s])
(require '[ring.swagger.schema :refer [coerce!]])
(s/defschema Bone {:size Long, :animal (s/enum :cow :tyrannosaurus)})
(coerce! Bone {:size 12, :animal "cow"})
; => {:animal :cow, :size 12}
(coerce! Bone {:animal :sheep})
; ExceptionInfo throw+: #schema.utils.ErrorContainer{:error {:animal (not (#{:tyrannosaurus :cow} :sheep)), :size missing-required-key}, :type :ring.swagger.schema/validation} ring.swagger.schema/coerce! (schema.clj:57)Currently there are two modes for coercions: :json and :query. Both coerce and coerce! take an optional third parameter (default to :json) to denote which coercer to use. You can also use the two coercers directly from namespace ring.swagger.coerce.
- numbers ->
LongorDouble - string -> Keyword
- string ->
java.util.Date,org.joda.time.DateTimeororg.joda.time.LocalDate - string ->
java.util.regex.Pattern - vectors -> Sets
Query-coercion extends the json-coercion with the following transformations:
- string -> Long
- string -> Double
- string -> Boolean
One can add extra meta-data, including descriptions to schema elements using ring.swagger.schema/field and ring.swagger.schema/describe functions. These work by adding meta-data to schema under :json-schema-key. Objects which don't support meta-data, like Java classes, are wrapped into s/both.
(require '[schema.core :as s])
(require '[ring.swagger.schema :as rs])
(require '[ring.swagger.json-schema :as rjs])
(s/defschema Customer {:id Long, :name (rs/describe String "the name")})
(rjs/json-schema-meta (describe Customer "The Customer"))
; => {:description "The Customer"})Copyright © 2014-2015 Metosin Oy
Distributed under the Eclipse Public License, the same as Clojure.