• Stars
    star
    360
  • Rank 118,230 (Top 3 %)
  • Language
    Clojure
  • Created almost 11 years ago
  • Updated almost 3 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Swagger Spec for Clojure Web Apps

Ring-Swagger Build Status Downloads

Swagger 2.0 implementation for Clojure/Ring using Plumatic Schema (support for clojure.spec via spec-tools).

  • Transforms deeply nested Schemas into Swagger JSON Schema definitions
  • Extended & symmetric JSON & String serialization & coercion
  • Middleware for handling Schemas Validation Errors & Publishing swagger-data
  • Local api validator
  • Swagger artifact generation

Latest version

Clojars Project

The CHANGELOG.

Requires Java 1.8+

Web libs using Ring-Swagger

Getting help

Clojurians slack (join) has a channel #ring-swagger for Ring-swagger related issues. You can also ask questions about Ring-swagger on other channels at Clojurians Slack or at #clojure on Freenode IRC (mention or ring-swagger to highlight us).

Info

Route definitions are expected as a clojure Map defined by the Schema Contract. The Schema allows mostly any extra keys as ring-swagger tries not to be on your way - one can pass any valid Swagger spec data in.

API Docs.

Simplest possible example

(require '[ring.swagger.swagger2 :as rs])

(rs/swagger-json {})

; {:swagger "2.0",
;  :info {:title "Swagger API", :version "0.0.1"},
;  :produces ["application/json"],
;  :consumes ["application/json"],
;  :paths {},
;  :definitions {}}

More complete example

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 {}}
             "/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 {:title "Sausages",
;         :version "1.0.0",
;         :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"}},
;  :produces ["application/json"],
;  :consumes ["application/json"],
;  :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 "id",
;                                             :description "",
;                                             :required true,
;                                             :type "string"}
;                                            {:in "body",
;                                             :name "User",
;                                             :description "",
;                                             :required true,
;                                             :schema {:$ref "#/definitions/User"}}],
;                               :responses {200 {:schema {:$ref "#/definitions/User"},
;                                                         :description "Found it!"},
;                                           404 {:description "Ohnoes."}}}}},
;  :definitions {"User" {:type "object",
;                        :properties {:id {:type "string"},
;                                     :name {:type "string"},
;                                     :address {:$ref "#/definitions/UserAddress"}},
;                        :additionalProperties false,
;                        :required (:id :name :address)},
;                "UserAddress" {:type "object",
;                               :properties {:street {:type "string"},
;                                                     :city {:type "string",
;                                                            :enum (:tre :hki)}},
;                               :additionalProperties false,
;                               :required (:street :city)}}}

producing the following ui:

ring-swagger

Customizing Swagger Spec output

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.

 :collection-format               - Sets the collectionFormat for query and formData
                                    parameters.
                                    Possible values: multi, ssv, csv, tsv, pipes."

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"}}}}}}

Validating the Swagger Spec

The generated full spec can be validated against the Swagger JSON Schema with the help of scjsv.

(require '[ring.swagger.validator :as v])

(v/validate (rs/swagger-json {:paths {"/api/ping" {:get nil}}}))
; nil

(v/validate (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.

Transforming the Swagger Spec

There are the following utility functions for transforming the spec (on the client side):

ring.swagger.swagger2/transform-operations - transforms the operations under the :paths of a ring-swagger spec by applying (f operation) to all operations. If the function returns nil, the given operation is removed.

As an example, one can filter away all operations with :x-no-doc set to true:

(defn remove-x-no-doc [endpoint]
  (if-not (some-> endpoint :x-no-doc true?)
    endpoint))

(transform-operations remove-x-no-doc {:paths {"/a" {:get {:x-no-doc true}, :post {}}
                                               "/b" {:put {:x-no-doc true}}}}))
; {:paths {"/a" {:post {}}}}

Web Schemas

Prismatic Schema is used to describe both the input & output schemas for routes.

As Swagger 2.0 Spec Schema is a deterministic subset of JSON Schema, so not all Clojure Schema elements can be used.

Schema to Swagger JSON Schema conversion

There are two possible methods to do this:

  1. class-based dispatch via ring.swagger.json-schema/convert-class.
  2. protocol-based dispatch via ring.swagger.json-schema/JsonSchema - the convert fn.

Both take the Schema and swagger options map as arguments. Options contain also :in to denote the possible location of the schema (nil, :query, :header, :path, :formData and :body).

To support truly symmetric web schemas, one needs also to ensure both JSON Serialization and deserialization/coercion from JSON.

Class-based dispatch

(require '[ring.swagger.json-schema :as json-schema])

(defmethod json-schema/convert-class java.sql.Date [_ _] {:type "string" :format "date"})

Protocol-based dispatch

(require '[ring.swagger.json-schema :as json-schema])

(extend-type java.util.regex.Pattern
  json-schema/JsonSchema
  (json-schema/convert [e _]
    {:type "string" :pattern (str e)}))

One can also use the options to create more accurate specs (via the :in option).

(extend-type schema.core.Maybe
  json-schema/JsonSchema
  (convert [e {:keys [in]}]
    (let [schema (->swagger (:schema e))]
      (if (#{:query :formData} in)
        (assoc schema :allowEmptyValue true)
        schema))))

Out-of-the-box supported Schema elements

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, Symbol, s/Symbol, s/Any non-body-parameter string "kikka"
Boolean boolean true
nil, s/Any body-parameter void
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"
java.util.Date, org.joda.time.DateTime, s/Inst, java.time.Instant string, date-time "2014-02-18T18:25:37.456Z", also without millis: "2014-02-18T18:25:37Z"
org.joda.time.LocalDate, java.time.LocalDate string, date "2014-02-19"
org.joda.time.LocalTime, java.time.LocalTime string, time "16:22"
(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/constrained X pred) type of X
(s/conditional p1 X p2 Y p3 Z) one of type X, Y, Z
(s/cond-pre X Y Z) one of type X, Y, Z
(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, enum(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
    • Maps can be within valid containers (as only element - heterogeneous schema sequences not supported by the spec)

Missing Schema elements

If Ring-swagger can't transform the Schemas into JSON Schemas, by default a IllegalArgumentException will be thrown. Setting the :ignore-missing-mappings? to true causes the errors to be ignored - missing schema elements will be ignored from the generated Swagger schema.

Body and Response model names

Standard Prismatic Schema names are used. Nested schemas are traversed and all found sub-schemas are named automatically - so that they can be referenced in the generated Swagger spec.

Swagger 2.0 squashes all api models into a single global namespace, so schema name collisions can happen. When this happens, the function defined by :handle-duplicate-schemas-fn option is called to resolve the collision. By default, the collisions are ignored.

One accidental reason for schema name collisions is the use of normal clojure.core functions to create transformed copies of the schemas. The normal core functions retain the original schema meta-data and by so the schema name.

(s/defschema User {:id s/Str, :name s/Str})
(def NewUser (dissoc User :id)) ; dissoc does not remove the schema meta-data

(meta User)
; {:name User :ns user}


(meta NewUser)
; {:name User :ns user} <--- fail, now there are two User-schemas around.

There are better schema transformers functions available at schema-tools. It's an implicit dependency of ring-swagger.

Extra Schema elements supported by ring.swagger.json-schema-dirty

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 JSON Schema dispatching for the following:

WARNING 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) x-oneOf: type of X, type of Y, type of Z
(s/if pred X Y) x-oneOf: type of X, type of Y

Schema coercion

Ring-swagger uses Schema coercions for transforming the input data into vanilla Clojure and back.

There are two coercers in ring.swagger.coerce, the json-schema-coercion-matcher and query-schema-coercion-matcher. These are enchanced versions of the original Schema coercers, adding support for all the supported Schema elements, including Dates & Regexps.

Custom Coercions

In order to allow for custom input coercion, ring-swagger includes a multimethod 'custom-matcher' that can be implemented for custom input types. For example, to coerce currency strings into joda.money.Money objects, you can implement the following:

(require '[ring.swagger.coerce :as coerce])
(import org.joda.money.Money)

(defmethod coerce/custom-matcher org.joda.money.Money  [_]  #(org.joda.money.Money/parse %))

This will allow org.joda.money.Money objects in your Schema definitions to be coerced correctly. However, this is only for coercing input, see Schema to Swagger JSON Schema conversion for examples on transforming output.

Coerce!

Ring-swagger provides a convenience function for coercion, ring.swagger.schema/coerce!. It returns either a valid coerced value of slingshots an Map with type :ring.swagger.schema/validation. One can catch these exceptions via ring.swagger.middleware/wrap-validation-errors and return a JSON-friendly map of the contents.

(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)

Adding description to Schemas

One can add extra meta-data, including descriptions to schema elements using ring.swagger.json-schema/field and ring.swagger.json-schema/describe functions. These work by adding meta-data to schema under :json-schema-key. Objects which don't natively support meta-data, like Java classes, are wrapped automatically into ring.swagger.json-schema/FieldSchema to enable the meta-data.

Example

(require '[schema.core :as s])
(require '[ring.swagger.schema :as rs])
(require '[ring.swagger.json-schema :as rjs])

(s/defschema Required
  (rjs/field
    {(s/optional-key :name) s/Str
     (s/optional-key :title) s/Str
     :address (rjs/field
                {:street (rsjs/field s/Str {:description "description here"})}
                {:description "Streename"
                 :example "Ankkalinna 1"})}
    {:minProperties 1
     :description "I'm required"
     :example {:name "Iines"
               :title "Ankka"}}))

; produces the following JSON Schema models =>
;
; {"Required" {:type "object"
;              :description "I'm required"
;              :example {:name "Iines"
;                        :title "Ankka"}
;              :minProperties 1
;              :required [:address]
;              :properties {:name {:type "string"}
;                           :title {:type "string"}
;                           :address {:$ref "#/definitions/RequiredAddress"}}
;              :additionalProperties false}
;  "RequiredAddress" {:type "object"
;                     :description "Streename"
;                     :example "Ankkalinna 1"
;                     :properties {:street {:type "string"
;                                           :description "description here"}}
;                     :required [:street]
;                     :additionalProperties false}}

License

Copyright © 2014-2018 Metosin Oy

Distributed under the Eclipse Public License, the same as Clojure.

More Repositories

1

malli

High-performance data-driven data specification library for Clojure/Script.
Clojure
1,499
star
2

reitit

A fast data-driven routing library for Clojure/Script
Clojure
1,313
star
3

spec-tools

Clojure(Script) tools for clojure.spec
Clojure
586
star
4

muuntaja

Clojure library for fast http api format negotiation, encoding and decoding.
Clojure
410
star
5

jsonista

Clojure library for fast JSON encoding and decoding.
Clojure
384
star
6

kekkonen

A remote (CQRS) API library for Clojure.
Clojure
220
star
7

tilakone

Minimalistic finite state machine (FSM) in Clojure
Clojure
192
star
8

sieppari

Small, fast, and complete interceptor library for Clojure/Script
Clojure
189
star
9

pohjavirta

Fast & Non-blocking Clojure wrapper for Undertow
Clojure
162
star
10

ring-http-response

Handling HTTP Statuses with Clojure(Script)
Clojure
139
star
11

schema-tools

Clojure(Script) tools for Plumatic Schema
Clojure
106
star
12

porsas

Experimental stuff for going fast with Clojure + JDBC & Async SQL
Clojure
95
star
13

talvi

Opinionated and Performant Web Application Stack for Clojure/Script
80
star
14

schema-viz

Plumatic Schema visualization using Graphviz.
Clojure
74
star
15

potpuri

Common clojure stuff.
Clojure
69
star
16

komponentit

Collection of bespoke Reagent components
Clojure
61
star
17

bat-test

Fast Clojure.test runner for Boot and Leiningen
Clojure
60
star
18

ring-swagger-ui

Swagger UI packaged for Ring Apps
Clojure
48
star
19

scjsv

Simple Clojure JSON-Schema Validator
Clojure
47
star
20

testit

Midje like assertions for Clojure.test
Clojure
43
star
21

compojure-api-examples

Compojure API example
Clojure
32
star
22

maailma

Opinionated environment variables library
Clojure
31
star
23

vega-tools

Utilities for working with Vega visualization grammar in ClojureScript.
Clojure
27
star
24

fnhouse-swagger

Swagger integration for fnhouse
Clojure
22
star
25

spec-swagger

Master Swagger2 & OpenAPI3 specs with Clojure(Script) & clojure.spec
Clojure
21
star
26

virhe

Beautiful Error Message for Clojure/Script
Clojure
20
star
27

reagent-dev-tools

Development tool panel for Reagent
Clojure
19
star
28

mallitaulut

Extract Malli schemas from SQL table schemas.
Clojure
17
star
29

eines

Simple Clojure and ClojureScript library for WebSocket communication
Clojure
16
star
30

sauna-todo

Simple full-stack TODO app example for demonstrating Clojure(script)
Clojure
16
star
31

packaging-clojure-examples

Packaging a full-stack Clojure web app for production
Clojure
16
star
32

loiste

Excellent Excel library
Clojure
16
star
33

clojure-bootcamp

Clojure
14
star
34

metosin-common

Random collection of various namespaces used in multiple Metosin projects
Clojure
13
star
35

viesti

Data-Driven Message Dispatcher for Clojure/Script
12
star
36

sormilla

Playing with Leap Motion and Parrot AR.Drone
Clojure
11
star
37

malli.io

Malli playground, https://malli.io
Clojure
11
star
38

lomakkeet

Proof of concept: Form library for Reagent
Clojure
11
star
39

c2

Demo about compojure-api2 stuff
Clojure
8
star
40

cloud-busting

Basis for using Terraform to manage application runtime in AWS
HCL
7
star
41

palikka

Opinionated component library
Clojure
7
star
42

tyylikas

Clojure linter and fixer
Clojure
6
star
43

oksa

Generate GraphQL queries using Clojure data structures.
Clojure
6
star
44

kekkonen-sample

Sample project With Kekkonen
Clojure
5
star
45

clojure-bootcamp-setup

Setup instructions for Metosin Clojure Bootcamp training
Clojure
5
star
46

web-schemas

Prismatic Schema extensions for the Web.
Clojure
5
star
47

compojure-intro

compojure-intro
Clojure
4
star
48

clj-suomi

A Clojure library designed to access Finnish code sets.
Clojure
4
star
49

lokit

Single dependency for logging on the JVM
Clojure
4
star
50

tom

Tom, a graph-based component library
Clojure
4
star
51

bootcamp-2019-04-08

Bootcamp 2019-04-08
Clojure
4
star
52

boot-deps-size

Boot task to check size of dependencies
Clojure
4
star
53

om-dev-tools

Clojure
4
star
54

compojure-api-template

Compojure Api Template
Clojure
4
star
55

bootcamp-2021-feb

Lessons and exercises for bootcamp in February 2021
Clojure
4
star
56

kekkonen-building-permit-example

a complex simulated real-life case example showcase project
Clojure
4
star
57

terraform-study-group

3
star
58

training-2023-05-32

Advanced Clojure training
Clojure
3
star
59

open-source

Home page for Metosin's open source development work
JavaScript
3
star
60

lein-simulflow

ABANDONED: Combine several lein auto tasks for leaner workflow.
Clojure
3
star
61

bootcamp-2018-03-15-sample-app

Sample app for bootcamp 2018-03-15
Clojure
3
star
62

linkit

Om.next + Kekkonen test
Clojure
3
star
63

clj-ai-meetup

Case Studies in AI for Clojure Tampere meetup
Clojure
3
star
64

bootcamp-2

Bootcamp 2
Clojure
2
star
65

bootcamp3

Refactored bootcamp
Clojure
2
star
66

clojure-koulutus-2023-01-24-esitehtavat

Clojure ja ClojureScript koulutus 2023-01-24 esitehtävät
Clojure
2
star
67

bootcamp-2018-05-04

Sample app for bootcamp at 2018-05-04
Clojure
2
star
68

clojure-bootcamp-20150130

Clojure Bootcamp 2015-01-30 for Affecto
Clojure
2
star
69

clojure-finland-2018-05-30-cljs-ws-demo

ClojureScript and WebSocket demo for Clojure Finland 2018-05-20 meetup
Clojure
2
star
70

juustometsae

vain käyttötarkoitus puuttuu
2
star
71

training-day-1

Example materials for first part of training
Clojure
2
star
72

2016-09-09-clojure-training

Material for 2016-09-09 intermediate Clojure training topics about Tooling and Workflow; and Full-stack apps
Clojure
2
star
73

postgres-tools

WIP Postgresql utilities
Clojure
2
star
74

boot-alt-http

Simple boot http server task to serve files from classpath.
Clojure
2
star
75

clojure-koulutus-2023-01-24

Koulutusmateriaali 2023-01-24 koulutukseen
Clojure
1
star
76

clojurebridge-intro

Intro project for ClojureBridge, in Finnish. Olkaa hyvä.
Clojure
1
star
77

clojure-bootcamp-intro

Bootcamp intro project
Clojure
1
star
78

reitit-example

Sample layout for reitit
Clojure
1
star
79

bootbook

BootBook :- Clojure Bootcamp book-store example
Clojure
1
star
80

docker-circle-convox

Dockerfile
1
star
81

docker-circle-lein

1
star
82

ks-example

Simple example project with clj and cljs
Clojure
1
star
83

rabbitmq-agent

Clojure
1
star
84

bootcamp-luminus-2021-feb

Luminus application for 2021 February bootcamp
Clojure
1
star
85

compojure-api-sample

Compojure-api sample project with Component
Clojure
1
star
86

2016-09-09-clojure-training-2

Material for 2016-09-09 intermediate Clojure training: perf, polymorfic, data, diy weblib
Clojure
1
star
87

clojurebridge-helsinki

Homepage for ClojureBridge Finland
CSS
1
star
88

bootcamp-20170314

Bootcamp example for 2017/03/14-16 bootcamp
Clojure
1
star
89

example-project

Clojure
1
star