Giter Site home page Giter Site logo

metosin / ring-swagger Goto Github PK

View Code? Open in Web Editor NEW
371.0 29.0 84.0 1.42 MB

Swagger Spec for Clojure Web Apps

Home Page: http://metosin.github.io/ring-swagger/doc/

Clojure 99.59% Shell 0.41%
clojure json-schema rest openapi schema-elements swagger metosin-stable

ring-swagger's Introduction

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

Release process

To release a version, set the project.clj version to the one you want to release, but with a -SNAPSHOT suffix.

Then create a commit reading "Release :{major,minor,patch,alpha,beta,rc}" based on whether you want the next development version to be a major/minor/patch/alpha/beta/rc increment.

Push to master, and the GitHub Actions release will release the jar to clojars, then bump the version on the master branch.

License

Copyright © 2014-2018 Metosin Oy

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

ring-swagger's People

Contributors

amalloy avatar bhagany avatar borkdude avatar cgrand avatar davidjameshumphreys avatar deraen avatar fbellomi avatar frankiesardo avatar frenchy64 avatar gwena avatar heimojuh avatar ikitommi avatar joakimlofgren avatar jqmtor avatar malcolmsparks avatar michaelblume avatar miikka avatar nha avatar nickmbailey avatar nicksieger avatar opqdonut avatar phadej avatar podviaznikov avatar smee avatar tiensonqin avatar tonyvanriet avatar vharmain avatar zonpantli avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ring-swagger's Issues

Use schema/start-walker to collect schemas

metosin/compojure-api#53

ring-swagger.core/collect-schemas is manually recurring through the schema to collect subschemas from a schema. This is used to add names to all unnamed subschemas.
This doesn't work when subschema is wrapped in e.g. s/Maybe. Using schema/start-walker would solve this as it knows how to walk s/Maybe & other schemas.

Keep order in :paths

Currently, swagger-json re-orders/hashes :paths. It should retain the :paths order given by the client (via flatland.ordered.map/ordered-map for example) as it's effects the swagger-ui ordering

Incorrect naming of s/either combinations

Hi,

I think I've found a bug where s/either schemas are deconstructed to take the first schema, but the name is taken from the last schema.

Here is an example:

(s/defschema SchemaA {:a s/Str})
(s/defschema SchemaB {:b s/Str})
(s/defschema SchemaAB (s/either SchemaA SchemaB))

(def swagger-object {:paths {"/ab" {:get {:parameters {:body SchemaAB}}}}})

(clojure.pprint/pprint (swagger-json swagger-object))

This gives the following Swagger json:

{:swagger "2.0",
 :info {:title "Swagger API", :version "0.0.1"},
 :produces ["application/json"],
 :consumes ["application/json"],
 :paths
 {"/ab"
  {:get
   {:parameters
    [{:in :body,
      :name "SchemaB",
      :description "",
      :required true,
      :schema {:$ref "#/definitions/SchemaA"}}],
    :responses {:default {:description ""}}}}},
 :definitions
 {"SchemaA"
  {:type "object", :properties {:a {:type "string"}}, :required (:a)},
  "SchemaB"
  {:type "object", :properties {:b {:type "string"}}, :required (:b)}}}

The body parameters are named SchemaB but the schema refers to SchemaA.
Expected behaviour is that the name would be SchemaA and it would point to SchemaA.

Only a minor bug, but it makes the Swagger UI confusing.

schema.core does not have defn as var

Exception in thread "main" java.lang.RuntimeException: No such var: s/defn, compiling:(ring/swagger/core.clj:294:1)

As it is seen on the top of page it uses [schema.core :as s]

And on 294
(s/defn ^:always-validate convert-response-messages [messages :- [ResponseMessage]]
...

This came up when schemas were stored into compiled jar file and used through dependencies. Preventes ring server to start up.

Primitive return types don't work

EDIT: I'm not sure, if this is ring-swagger or compojure-api bug. Found while playing with [metosin/compojure-api "0.13.0"]

      (GET* "/ping/:str" []
        :return       String
        :path-params  [str :- String]
        :summary      "Like echo, but with string"
        (ok str))

fails with

Caused by: java.lang.ClassCastException: java.lang.Class cannot be cast to clojure.lang.IObj
    at clojure.core$with_meta.invoke(core.clj:214)
    at schema.core$schema_with_name.invoke(core.clj:953)
    at compojure.api.swagger$ensure_return_schema_names$fn__7999.invoke(swagger.clj:150)
    at ring.swagger.impl$update_schema.invoke(impl.clj:23)
    at clojure.lang.AFn.applyToHelper(AFn.java:156)
    at clojure.lang.AFn.applyTo(AFn.java:144)
    at clojure.core$apply.invoke(core.clj:626)
    at clojure.core$update_in.doInvoke(core.clj:5698)
    at clojure.lang.RestFn.applyTo(RestFn.java:146)
    at clojure.core$apply.invoke(core.clj:630)
    at clojure.core$update_in.doInvoke(core.clj:5697)
    at clojure.lang.RestFn.invoke(RestFn.java:467)
    at compojure.api.swagger$ensure_return_schema_names.invoke(swagger.clj:147)
    at compojure.api.swagger$attach_meta_data_to_route.invoke(swagger.clj:158)

Returning primitives should be somehow supported? (e.g.: in http://petstore.swagger.wordnik.com/#!/user/loginUser)

I could workaround the issue by wrapping primitives into "s/defschema", but it's not elegant.

Null default value breaks swagger-ui

Default value from Plumbing forms is now user for JSON Schema default value. Swagger-ui however doesn't support null values. Plumbing optional values set null as default value currently.

Extend Supported Schema elements for s/Any

Let s/Any be a Supported Schema element with JSON Schema value of void, much like nil is currently.

When using nil, the following is produced in the console when the endpoint is queried against:
java.lang.IllegalArgumentException: No implementation of method: :walker of protocol: #'schema.core/Schema found for class: nil
However, using s/Any produces an error on the swagger-ui page:

Unable to read api 'Testswagger' from path http://localhost:3000/api/api-docs/Testswagger (server returned undefined)

Named schemas issues

Hi guys,

I've got a couple of issues opened on my project that look like they are coming from ring-swagger:

If you reuse a schema definition (e.g. by merging something to it) it seems ring-swagger overrides the #ref attribute for that schema. So if you have a Bar {:name Str} and BarList [(assoc Bar id Int)] all the Bar will have an id. See frankiesardo/route-swagger#7

Possibly in a related area: if you define an anonymous schema inline the key for it is not generated. See frankiesardo/route-swagger#6

I'll try to reproduce these issues in a vacuum when I have some time, but let me know if this rings a bell.

populating swagger description from schema docstring

Hi,

I have defined my schemas using defschema and have given them docstrings. When generating the swagger json, I dont see the docstring being extracted.

The swagger spec provides a :description key, is it possible to populate that key using the docstring for a given schema ?

Thanks,
Murtaza

Refactor json-type multimethods to a protocol

While performance doesn't matter in this case, protocols would make sense because creating new schema types would be easier:

(defrecord RequiredSchema [s]
  s/Schema
  (walker [this]
    (let [sub-walker (s/subschema-walker s)]
      (fn [x]
        (if (seq x)
          (sub-walker x)
          (smacros/validation-error this x (list 'required (su/value-name x)))))))
  (explain [this]
    (list 'required (s/explain s)))
  rs/JsonType
  (json-type [this] (rs/json-type (:s this)))

X-Forwarded headers

In addition to X-Forwarded-Proto which is already supported:

context

  • X-Forwarded-Context (add to context)

basepath

  • Looks like this is not used by compojure-api
  • X-Forwarded-Port
  • X-Forwarded-Host (?)

The json-type multimethod's dispatch is inflexible.

Problem: The json-type multimethod dispatch is inflexible. There are two use cases that I'd like to be able to do that it's preventing.

  1. I'd like to be able to use an s/Any schema on one of my fields.
  2. I'd like to be able to attach descriptions to fields at arbitrary depths in recursive schemas.

Solution: Use type in json-type's dispatch function.

(defmulti json-type
  (fn [e]
    (if (instance? Class e)
      e
      (type e))))

Example Use Case:

(s/defschema Note
  (with-meta s/Any
    {:type ::note}))

(defmethod swagger/json-type ::note
  [_]
  {:type "object"
   :description "Arbitrary JSON. Can be just a string."})

(s/defschema Discount
  {:amount Price
   :type (s/enum "absolute")
   (s/optional-key :note) Note})

NOTE: I realize now that one can use field to attach descriptions at arbitrary depths. There's still no way to accomplish the use of s/Any though.

Support all collectionFormats

Swagger spec defines multiple ways to handle collections in string parameters (query, header, url encoded form params):
https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#parameter-object

Currently only multi format is supported. This works fine for query strings where the same property is repeated for each value: ?items=a&items=b&items=c. For this case we have coercion to coerce single item to collection when needed.

Some parameters (headers) don't support this multi format and instead the collection needs to be represented as single string with separators. For this we need an coercer that can split an string using selected separator. This coercion should only be done when needed. Probably based on Schema record. For users, the API should be something like: :header-params [items (collection [s/Str] :csv)].

Determines the format of the array if type array is used. Possible values are:
csv - comma separated values foo,bar.
ssv - space separated values foo bar.
tsv - tab separated values foo\tbar.
pipes - pipe separated values foo|bar.
multi - corresponds to multiple parameter instances instead of multiple values for a single instance foo=bar&foo=baz. This is valid only for parameters in "query" or "formData".

Body parameters of type Array don't seem to be exposed in swagger.json

Hi! I'm trying to generate swagger through the compoure-api project. I've found that schemas that I set on body parameters which validate Clojure hash-maps wind up generating an { "in": "body" } section in the parameters section for the endpoint, and everything has been working well with that.

But when I have a body parameter which accepts a JSON array as its top-level element, rather than a JSON object, I don't see the body parameter exposed in the resulting swagger.json.

I haven't managed to extract my code from compojure-api at the moment, but the relevant compojure-api bits resemble this:

(defapi my-api
  (PUT* "/map" []
    ;; The following parameter is exposed in swagger.json
    :body [body {:foo s/Str}]
    (ok))
  (PUT* "/array" []
    ;; The following parameter is NOT exposed in swagger.json
    :body [body [s/Str]]
    (ok)))

If it's helpful I can try to come up with a smaller test-case or sample project. Thanks!

Error when using s/either with types

Thanks for the awesome library!

I am having trouble when I use s/either or s/enum with types:

(def Ident (s/either Long s/Keyword))

This works as expected with s/validate, but ring-swagger throws:

java.lang.IllegalArgumentException
error converting to json schema [:db/ident (either java.lang.Long Keyword)]

core.clj:127    ring.swagger.core/properties[fn]
core.clj:123    ring.swagger.core/properties[fn]
LazySeq.java:42 clojure.lang.LazySeq.sval
LazySeq.java:60 clojure.lang.LazySeq.seq
RT.java:484 clojure.lang.RT.seq
core.clj:133    clojure.core/seq
protocols.clj:30    clojure.core.protocols/seq-reduce
protocols.clj:54    clojure.core.protocols/fn
protocols.clj:13    clojure.core.protocols/fn[fn]
core.clj:6177   clojure.core/reduce
core.clj:6229   clojure.core/into
core.clj:119    ring.swagger.core/properties
core.clj:145    ring.swagger.core/transform
core.clj:2487   clojure.core/map[fn]
LazySeq.java:42 clojure.lang.LazySeq.sval
LazySeq.java:60 clojure.lang.LazySeq.seq
RT.java:484 clojure.lang.RT.seq
core.clj:133    clojure.core/seq
core.clj:2479   clojure.core/map[fn]
LazySeq.java:42 clojure.lang.LazySeq.sval
LazySeq.java:60 clojure.lang.LazySeq.seq
RT.java:484 clojure.lang.RT.seq
core.clj:133    clojure.core/seq
protocols.clj:30    clojure.core.protocols/seq-reduce
protocols.clj:54    clojure.core.protocols/fn
protocols.clj:13    clojure.core.protocols/fn[fn]
core.clj:6177   clojure.core/reduce
core.clj:6229   clojure.core/into
core.clj:163    ring.swagger.core/transform-models
RestFn.java:137 clojure.lang.RestFn.applyTo
core.clj:617    clojure.core/apply
core.clj:275    ring.swagger.core/api-declaration
swagger.clj:54  fnhouse.swagger/fn[fn]
swagger.clj:49  fnhouse.swagger/fn[fn]
swagger.clj:49  fnhouse.swagger/fn[fn]
AFn.java:161    clojure.lang.AFn.applyToHelper
AFn.java:151    clojure.lang.AFn.applyTo
AFunction.java:29   clojure.lang.AFunction$1.doInvoke
RestFn.java:408 clojure.lang.RestFn.invoke
handlers.clj:224    fnhouse.handlers/eval4108[fn]
AFn.java:161    clojure.lang.AFn.applyToHelper
AFn.java:151    clojure.lang.AFn.applyTo
AFunction.java:29   clojure.lang.AFunction$1.doInvoke
RestFn.java:408 clojure.lang.RestFn.invoke
handlers.clj:198    fnhouse.handlers/eval4027[fn]
AFn.java:161    clojure.lang.AFn.applyToHelper
AFn.java:151    clojure.lang.AFn.applyTo
AFunction.java:29   clojure.lang.AFunction$1.doInvoke
RestFn.java:408 clojure.lang.RestFn.invoke
middleware.clj:109  fnhouse.middleware/eval4505[fn]
routes.clj:131  fnhouse.routes/eval3892[fn]
routes.clj:128  fnhouse.routes/eval3892[fn]
AFn.java:161    clojure.lang.AFn.applyToHelper
AFn.java:151    clojure.lang.AFn.applyTo
AFunction.java:29   clojure.lang.AFunction$1.doInvoke
RestFn.java:408 clojure.lang.RestFn.invoke
stacktrace.clj:80   ring.middleware.stacktrace/wrap-stacktrace-web[fn]
ring.clj:20 api.ring/keywordize-middleware[fn]
json.clj:35 ring.middleware.json/wrap-json-body[fn]
params.clj:58   ring.middleware.params/wrap-params[fn]
json.clj:65 ring.middleware.json/wrap-json-response[fn]
ring.clj:11 api.ring/wrap-exception[fn]
resource.clj:24 ring.middleware.resource/wrap-resource[fn]
reload.clj:18   ring.middleware.reload/wrap-reload[fn]
RingHandler.java:33 org.httpkit.server.HttpHandler.run
Executors.java:471  java.util.concurrent.Executors$RunnableAdapter.call
FutureTask.java:262 java.util.concurrent.FutureTask.run
ThreadPoolExecutor.java:1145    java.util.concurrent.ThreadPoolExecutor.runWorker
ThreadPoolExecutor.java:615 java.util.concurrent.ThreadPoolExecutor$Worker.run
Thread.java:744 java.lang.Thread.run

Cannot get recursive types to work

I see that recursive types should be supported, but I can't get them to work.
Even a basic schema like:

(declare Bar)

(def Foo {:bar (s/recursive #'Bar)})

(def Bar {:foo (s/maybe Foo)})

Throws an exception when accessing the api-docs:

don't know how to create json-type of: {:bar (recursive (var my.fully.qualified.path/Bar))}

Is there anything I should be aware of when declaring recursive types?
I'm using ring-swagger 0.12.0

File upload support

  • File Schema which has to-json implementation. {:type "file"}
    • "in": "formData" ?
  • If type is "file", the consumes MUST be either "multipart/form-data" or " application/x-www-form-urlencoded" and the parameter MUST be in "formData".

Ring-swagger fails on non-map schemas

How to reproduce:

(defschema Foo (enum :one :two))

(defschema Bar {:key Foo})

It fails with a following error:

java.lang.UnsupportedOperationException: Can't create empty: schema.core.EnumSchema
    at schema.core.EnumSchema.empty(core.clj:285)
    at clojure.core$empty.invoke(core.clj:4797)
    at ring.swagger.json_schema$properties.invoke(json_schema.clj:111)
    at ring.swagger.core$transform.invoke(core.clj:91)
    at clojure.core$comp$fn__4192.invoke(core.clj:2403)
    at clojure.core$juxt$fn__4211.invoke(core.clj:2440)
    at clojure.core$map$fn__4245.invoke(core.clj:2559)
…

In fact, this blows because of this line:

(into (empty schema)

Enum schema doesn't support empty, proof: https://github.com/Prismatic/schema/blob/master/src/cljx/schema/core.cljx#L285

P.S. I'm sorry that I didn't fix the bug myself and/or didn't help with 2.0 release as I intended to. Had too much to do recently, had to postpone opensource contributions.

:namespaced/keywords are not supported

Clojure now has support for namespaced keywords, but ring-swagger doesn't seem to have caught up. This means that if you are using them in your api they are stripped to be just the keyword without the namespace, and in order to use swagger you have to manually add the namespaces back in. I took a look through the code but it wasn't clear to me how to change this behavior.

Support new Schemas

  • CondPre
  • ConditionalSchema
  • experimental AbstractSchema and SchemaExtension

Extra Colon in swagger-ui for query-params

I'm working with a modified (work internal) version of liberator, so this could be part of my really weird setup.

In an attempt to get compojure-api working with liberator, I hit a weird issue.

The UI would populate, and show my required query-param field, and allow me to enter a value in it. When I clicked the "Try it out" button, my request went out with no query-params at all.

This was confirmed with the Web Inspector in Chrome.

Taking a look at the ui, I thought it was odd that the parameter type field in the UI said ":query", since the ui is mostly a java app.

The swagger.json was being generated incorrectly.

"parameters":[{"in":":query","name":"q","description":"","required":true,"type":"string"}]

Swagger 2.0

First of all, great job guys.
I'm implementing a swagger adapter for pedestal. The fact that pedestal gives you access to the entire route table is quite handy and gives you the ability of generating the entire documentation at compile time (incidentally Swagger 2.0 went the same way, preferring one big document rather than splitting informations on multiple endpoints) and that goes a long way towards testing a portability.
Now I'm wondering if you have plans to support Swagger 2.0, so that I'd rather start moving the development towards the new spec rather than polishing 1.2.
Keep up the good work.

swagger-ui path parameter conflicting with dot extensions like ".json"

Some of our service endpoints needs to accept a path parameter followed by an optional .json extension, e.g.

http://foo.com/api/id-12345.json

Currently ring-swagger produces uri's that does not look quite right, e.g.

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

user=> (rs/swagger-json 
          {:paths {"/api/:id.json"
           {:get {:parameters {:path {:id String}}}}}})
{:swagger "2.0",
 :info {:title "Swagger API", :version "0.0.1"},
 :produces ["application/json"],
 :consumes ["application/json"],
 :paths {"/api/{id.json}"  ;; <= HERE uh-oh
  {:get {:parameters [{:in :path, :name "id", :description "", :required true, :type "string"}], :responses {:default {:description ""}}}}}, :definitions {}}

For me, "/api/{id}.json" looks better than "/api/{id.json}".

Support for `tags` to describe and group paths

http://petstore.swagger.io/v2/swagger.json

{
    "tags":[
        {
            "name":"pet",
            "description":"Everything about your Pets",
            "externalDocs":{
                "description":"Find out more",
                "url":"http://swagger.io"
            }
        },
        {
            "name":"store",
            "description":"Operations about user"
        },
        {
            "name":"user",
            "description":"Access to Petstore orders",
            "externalDocs":{
                "description":"Find out more about our store",
                "url":"http://swagger.io"
            }
        }
    ]
}

Error while compiling

Hi,

I have included the 0.22 version of ring-swagger. When I try to evaluate the following code -

(ns my.proj
 (:require [ring.swagger.swagger2 :as rs]))

I get the following error -

 Error compiling: ring/swagger/json_schema.clj:1:1 Could not locate
   linked/core__init.class or linked/core.clj on classpath:

Could you help me out ?

Schema validation and online validator

Hi guys,

Finally I got some time again on my hands to play with pedestal and swagger . I just noticed that my newly deployed sample fails the online swagger validator. I can't really understand what that json schema error is telling me, but my generated map pass the schema check from ring.swagger.swagger2/swagger-json. So either the json schema is wrong, or the prismatic schema is wrong, or I am doing something wrong. As a result I get some weird model parameters in the swagger-ui.

Any help appreciated, and as always thanks for the work you're putting in this library.

Double data type doesn't accept integers or BigDecimals

Datatype Double doesn't accept integers or bigdecimals. This is bit awkward when you are returning values to caller. Compojure-api validates also responses against schema.

If I set value of Double field to integer or bigdecimal, I get these validation errors when returning response:

"(not (double? 2))"
"(not (double? 2M))"

Maybe Double datatype should also accept integers and bigdecimals, or there should be a new datatype that accepts integers, doubles, bigdecimals etc.

Long datatype works just fine with BigIntegers.

Double datatype:
https://github.com/metosin/ring-swagger/blob/master/src/ring/swagger/data.clj#L9

Long datatype:
https://github.com/metosin/ring-swagger/blob/master/src/ring/swagger/data.clj#L8
https://github.com/Prismatic/schema/blob/master/src/cljx/schema/core.cljx#L363

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.