mirror of
https://github.com/davidebianchi/gswagger.git
synced 2026-06-11 13:34:15 -04:00
Merge pull request #2 from davidebianchi/handle-request-response
This commit is contained in:
14
README.md
14
README.md
@@ -4,3 +4,17 @@ Initial test&try to generate a swagger dynamically.
|
||||
|
||||
It uses [gorilla-mux](https://github.com/gorilla/mux) and [kin-openapi](https://github.com/getkin/kin-openapi)
|
||||
to automatically generate and serve a swagger file.
|
||||
|
||||
To convert struct to schemas, we use [this library](https://github.com/alecthomas/jsonschema).
|
||||
The struct should contains the appropriate struct tags to be inserted in json schema.
|
||||
|
||||
### FAQ
|
||||
|
||||
1. How to add format `binary`?
|
||||
Formats `date-time`, `email`, `hostname`, `ipv4`, `ipv6`, `uri` could be added with tag `jsonschema`. Others format could be added with tag `jsonschema_extra`. Not all the formats are supported (see discovered unsupported formats [here](#discovered-unsupported-schema-features)).
|
||||
|
||||
#### Discovered unsupported schema features
|
||||
|
||||
*Formats*:
|
||||
|
||||
* `uuid` is unsupported by kin-openapi
|
||||
|
||||
4
go.mod
4
go.mod
@@ -3,7 +3,9 @@ module github.com/davidebianchi/gorilla-swagger
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921
|
||||
github.com/getkin/kin-openapi v0.22.1
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/stretchr/testify v1.5.1
|
||||
github.com/iancoleman/orderedmap v0.1.0 // indirect
|
||||
github.com/stretchr/testify v1.6.1
|
||||
)
|
||||
|
||||
45
go.sum
45
go.sum
@@ -1,49 +1,30 @@
|
||||
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921 h1:T3+cD5fYvuH36h7EZq+TDpm+d8a6FSD4pQsbmuGGQ8o=
|
||||
github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921/go.mod h1:/n6+1/DWPltRLWL/VKyUxg6tzsl5kHUCcraimt4vr60=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/getkin/kin-openapi v0.22.1 h1:ODA1olTp175o//NfHko/uCAAhwUSfm5P4+K52XvTg4w=
|
||||
github.com/getkin/kin-openapi v0.22.1/go.mod h1:WGRs2ZMM1Q8LR1QBEwUxC6RJEfaBcD0s+pcEVXFuAjw=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonreference v0.19.4 h1:3Vw+rh13uq2JFNxgnMTGE1rnoieU9FmyE1gvnyylsYg=
|
||||
github.com/go-openapi/jsonreference v0.19.4/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg=
|
||||
github.com/go-openapi/spec v0.19.9 h1:9z9cbFuZJ7AcvOHKIY+f6Aevb4vObNDkTEyoMfO7rAc=
|
||||
github.com/go-openapi/spec v0.19.9/go.mod h1:vqK/dIdLGCosfvYsQV3WfC7N3TiZSnGY2RZKoFK7X28=
|
||||
github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 h1:i462o439ZjprVSFSZLZxcsoAe592sZB1rci2Z8j4wdk=
|
||||
github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA=
|
||||
github.com/iancoleman/orderedmap v0.1.0 h1:2orAxZBJsvimgEBmMWfXaFlzSG2fbQil5qzP3F6cCkg=
|
||||
github.com/iancoleman/orderedmap v0.1.0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
88
integration_test.go
Normal file
88
integration_test.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package swagger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
swaggerOpenapiTitle = "test swagger title"
|
||||
swaggerOpenapiVersion = "test swagger version"
|
||||
)
|
||||
|
||||
func TestIntegration(t *testing.T) {
|
||||
t.Run("router works correctly", func(t *testing.T) {
|
||||
muxRouter := setupSwagger(t)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest(http.MethodGet, "/hello", nil)
|
||||
|
||||
muxRouter.ServeHTTP(w, r)
|
||||
|
||||
require.Equal(t, http.StatusOK, w.Result().StatusCode)
|
||||
|
||||
body := readBody(t, w.Result().Body)
|
||||
require.Equal(t, "OK", body)
|
||||
|
||||
t.Run("and generate swagger", func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest(http.MethodGet, JSONDocumentationPath, nil)
|
||||
|
||||
muxRouter.ServeHTTP(w, r)
|
||||
|
||||
require.Equal(t, http.StatusOK, w.Result().StatusCode)
|
||||
|
||||
body := readBody(t, w.Result().Body)
|
||||
require.Equal(t, "{\"components\":{},\"info\":{\"title\":\"test swagger title\",\"version\":\"test swagger version\"},\"openapi\":\"3.0.0\",\"paths\":{\"/hello\":{\"get\":{\"responses\":{\"default\":{\"description\":\"\"}}}}}}", body)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func readBody(t *testing.T, requestBody io.ReadCloser) string {
|
||||
t.Helper()
|
||||
|
||||
body, err := ioutil.ReadAll(requestBody)
|
||||
require.NoError(t, err)
|
||||
|
||||
return string(body)
|
||||
}
|
||||
|
||||
func setupSwagger(t *testing.T) *mux.Router {
|
||||
t.Helper()
|
||||
|
||||
context := context.Background()
|
||||
muxRouter := mux.NewRouter()
|
||||
|
||||
router, err := New(muxRouter, Options{
|
||||
Context: context,
|
||||
Openapi: &openapi3.Swagger{
|
||||
Info: &openapi3.Info{
|
||||
Title: swaggerOpenapiTitle,
|
||||
Version: swaggerOpenapiVersion,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
handler := func(w http.ResponseWriter, req *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(`OK`))
|
||||
}
|
||||
operation := Operation{}
|
||||
|
||||
_, err = router.AddRawRoute(http.MethodGet, "/hello", handler, operation)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = router.GenerateAndExposeSwagger()
|
||||
require.NoError(t, err)
|
||||
|
||||
return muxRouter
|
||||
}
|
||||
147
main.go
147
main.go
@@ -7,7 +7,6 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
"github.com/getkin/kin-openapi/openapi3filter"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
@@ -21,93 +20,24 @@ var (
|
||||
const (
|
||||
// JSONDocumentationPath is the path of the swagger documentation in json format.
|
||||
JSONDocumentationPath = "/documentation/json"
|
||||
|
||||
defaultOpenapiVersion = "3.0.0"
|
||||
)
|
||||
|
||||
// Router handle the gorilla mux router and the swagger schema
|
||||
type Router struct {
|
||||
router *mux.Router
|
||||
SwaggerSchema *openapi3.Swagger
|
||||
enableRequestValidation bool
|
||||
context context.Context
|
||||
swaggerRouter *openapi3filter.Router
|
||||
router *mux.Router
|
||||
swaggerSchema *openapi3.Swagger
|
||||
context context.Context
|
||||
}
|
||||
|
||||
// Handler is the http type handler
|
||||
type Handler func(w http.ResponseWriter, req *http.Request)
|
||||
|
||||
// GenerateAndExposeSwagger creates a /documentation/json route on router and
|
||||
// expose the generated swagger
|
||||
func (r Router) GenerateAndExposeSwagger() error {
|
||||
if err := r.SwaggerSchema.Validate(r.context); err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrValidatingSwagger, err)
|
||||
}
|
||||
|
||||
jsonSwagger, err := r.SwaggerSchema.MarshalJSON()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrGenerateSwagger, err)
|
||||
}
|
||||
|
||||
r.router.HandleFunc(JSONDocumentationPath, func(w http.ResponseWriter, req *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Header().Set("content-type", "application/json")
|
||||
w.Write(jsonSwagger)
|
||||
})
|
||||
// TODO: add yaml endpoint
|
||||
|
||||
err = r.swaggerRouter.AddSwagger(r.SwaggerSchema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddRoute add route to router with specific method, path and handler. Add the
|
||||
// router also to the swagger schema, after validating it
|
||||
func (r Router) AddRoute(method string, path string, handler Handler, operation Operation) (*mux.Route, error) {
|
||||
if operation.Operation != nil {
|
||||
err := operation.Validate(r.context)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
operation.Operation = openapi3.NewOperation()
|
||||
operation.Responses = openapi3.NewResponses()
|
||||
}
|
||||
r.SwaggerSchema.AddOperation(path, method, operation.Operation)
|
||||
|
||||
if operation.Operation != nil && r.enableRequestValidation {
|
||||
return r.router.HandleFunc(path, func(h http.ResponseWriter, req *http.Request) {
|
||||
err := validateRequest(r, req)
|
||||
if err != nil {
|
||||
// TODO: add response for validation response
|
||||
return
|
||||
}
|
||||
handler(h, req)
|
||||
|
||||
}).Methods(method), nil
|
||||
}
|
||||
return r.router.HandleFunc(path, func(w http.ResponseWriter, req *http.Request) {
|
||||
// Handle, when content-type is json, the request/response marshalling? Maybe with a specific option.
|
||||
handler(w, req)
|
||||
}).Methods(method), nil
|
||||
}
|
||||
|
||||
func (r Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
r.router.ServeHTTP(w, req)
|
||||
}
|
||||
|
||||
// RouterOptions to be passed to create the new router and swagger
|
||||
type RouterOptions struct {
|
||||
Context context.Context
|
||||
EnableRequestValidation bool
|
||||
Openapi *openapi3.Swagger
|
||||
// Options to be passed to create the new router and swagger
|
||||
type Options struct {
|
||||
Context context.Context
|
||||
Openapi *openapi3.Swagger
|
||||
}
|
||||
|
||||
// New generate new router with swagger. Default to OpenAPI 3.0.0
|
||||
func New(router *mux.Router, options RouterOptions) (*Router, error) {
|
||||
func New(router *mux.Router, options Options) (*Router, error) {
|
||||
swagger, err := generateNewValidSwagger(options.Openapi)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %s", ErrValidatingSwagger, err)
|
||||
@@ -119,53 +49,25 @@ func New(router *mux.Router, options RouterOptions) (*Router, error) {
|
||||
}
|
||||
|
||||
return &Router{
|
||||
router: router,
|
||||
enableRequestValidation: options.EnableRequestValidation,
|
||||
SwaggerSchema: swagger,
|
||||
context: ctx,
|
||||
swaggerRouter: openapi3filter.NewRouter(),
|
||||
router: router,
|
||||
swaggerSchema: swagger,
|
||||
context: ctx,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Operation type
|
||||
type Operation struct {
|
||||
*openapi3.Operation
|
||||
// TODO: handle request and response
|
||||
}
|
||||
|
||||
func validateRequest(r Router, req *http.Request) error {
|
||||
// Find route
|
||||
route, pathParams, err := r.swaggerRouter.FindRoute(req.Method, req.URL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Validate request
|
||||
requestValidationInput := &openapi3filter.RequestValidationInput{
|
||||
Request: req,
|
||||
PathParams: pathParams,
|
||||
Route: route,
|
||||
// TODO: add query params
|
||||
}
|
||||
|
||||
return openapi3filter.ValidateRequest(req.Context(), requestValidationInput)
|
||||
}
|
||||
|
||||
func generateNewValidSwagger(swagger *openapi3.Swagger) (*openapi3.Swagger, error) {
|
||||
if swagger == nil {
|
||||
swagger = &openapi3.Swagger{
|
||||
OpenAPI: defaultOpenapiVersion,
|
||||
}
|
||||
return nil, fmt.Errorf("swagger is required")
|
||||
}
|
||||
if swagger.OpenAPI == "" {
|
||||
swagger.OpenAPI = defaultOpenapiVersion
|
||||
}
|
||||
|
||||
if swagger.Paths == nil {
|
||||
swagger.Paths = openapi3.Paths{}
|
||||
}
|
||||
|
||||
if swagger.Info == nil {
|
||||
return nil, fmt.Errorf("swagger info must not be empty")
|
||||
return nil, fmt.Errorf("swagger info is required")
|
||||
}
|
||||
if swagger.Info.Title == "" {
|
||||
return nil, fmt.Errorf("swagger info title is required")
|
||||
@@ -176,3 +78,24 @@ func generateNewValidSwagger(swagger *openapi3.Swagger) (*openapi3.Swagger, erro
|
||||
|
||||
return swagger, nil
|
||||
}
|
||||
|
||||
// GenerateAndExposeSwagger creates a /documentation/json route on router and
|
||||
// expose the generated swagger
|
||||
func (r Router) GenerateAndExposeSwagger() error {
|
||||
if err := r.swaggerSchema.Validate(r.context); err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrValidatingSwagger, err)
|
||||
}
|
||||
|
||||
jsonSwagger, err := r.swaggerSchema.MarshalJSON()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrGenerateSwagger, err)
|
||||
}
|
||||
r.router.HandleFunc(JSONDocumentationPath, func(w http.ResponseWriter, req *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(jsonSwagger)
|
||||
})
|
||||
// TODO: add yaml endpoint
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
222
main_test.go
222
main_test.go
@@ -2,10 +2,11 @@ package swagger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
@@ -13,76 +14,167 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
swaggerOpenapiTitle = "test swagger title"
|
||||
swaggerOpenapiVersion = "test swagger version"
|
||||
)
|
||||
func TestNewRouter(t *testing.T) {
|
||||
mRouter := mux.NewRouter()
|
||||
|
||||
func TestIntegration(t *testing.T) {
|
||||
t.Run("router works correctly", func(t *testing.T) {
|
||||
router := setupSwagger(t)
|
||||
info := &openapi3.Info{
|
||||
Title: "my title",
|
||||
Version: "my version",
|
||||
}
|
||||
openapi := &openapi3.Swagger{
|
||||
Info: info,
|
||||
Paths: openapi3.Paths{},
|
||||
}
|
||||
|
||||
t.Run("not ok - invalid Openapi option", func(t *testing.T) {
|
||||
r, err := New(mRouter, Options{})
|
||||
|
||||
require.Nil(t, r)
|
||||
require.EqualError(t, err, fmt.Sprintf("%s: swagger is required", ErrValidatingSwagger))
|
||||
})
|
||||
|
||||
t.Run("ok - with default context", func(t *testing.T) {
|
||||
r, err := New(mRouter, Options{
|
||||
Openapi: openapi,
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, &Router{
|
||||
context: context.Background(),
|
||||
router: mRouter,
|
||||
swaggerSchema: openapi,
|
||||
}, r)
|
||||
})
|
||||
|
||||
t.Run("ok - with custom context", func(t *testing.T) {
|
||||
type key struct{}
|
||||
ctx := context.WithValue(context.Background(), key{}, "value")
|
||||
r, err := New(mRouter, Options{
|
||||
Openapi: openapi,
|
||||
Context: ctx,
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, &Router{
|
||||
context: ctx,
|
||||
router: mRouter,
|
||||
swaggerSchema: openapi,
|
||||
}, r)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGenerateValidSwagger(t *testing.T) {
|
||||
t.Run("not ok - empty swagger info", func(t *testing.T) {
|
||||
swagger := &openapi3.Swagger{}
|
||||
|
||||
swagger, err := generateNewValidSwagger(swagger)
|
||||
require.Nil(t, swagger)
|
||||
require.EqualError(t, err, "swagger info is required")
|
||||
})
|
||||
|
||||
t.Run("not ok - empty info title", func(t *testing.T) {
|
||||
swagger := &openapi3.Swagger{
|
||||
Info: &openapi3.Info{},
|
||||
}
|
||||
|
||||
swagger, err := generateNewValidSwagger(swagger)
|
||||
require.Nil(t, swagger)
|
||||
require.EqualError(t, err, "swagger info title is required")
|
||||
})
|
||||
|
||||
t.Run("not ok - empty info version", func(t *testing.T) {
|
||||
swagger := &openapi3.Swagger{
|
||||
Info: &openapi3.Info{
|
||||
Title: "title",
|
||||
},
|
||||
}
|
||||
|
||||
swagger, err := generateNewValidSwagger(swagger)
|
||||
require.Nil(t, swagger)
|
||||
require.EqualError(t, err, "swagger info version is required")
|
||||
})
|
||||
|
||||
t.Run("ok - custom swagger", func(t *testing.T) {
|
||||
swagger := &openapi3.Swagger{
|
||||
Info: &openapi3.Info{},
|
||||
}
|
||||
|
||||
swagger, err := generateNewValidSwagger(swagger)
|
||||
require.Nil(t, swagger)
|
||||
require.EqualError(t, err, "swagger info title is required")
|
||||
})
|
||||
|
||||
t.Run("not ok - swagger is required", func(t *testing.T) {
|
||||
swagger, err := generateNewValidSwagger(nil)
|
||||
require.Nil(t, swagger)
|
||||
require.EqualError(t, err, "swagger is required")
|
||||
})
|
||||
|
||||
t.Run("ok", func(t *testing.T) {
|
||||
info := &openapi3.Info{
|
||||
Title: "my title",
|
||||
Version: "my version",
|
||||
}
|
||||
swagger := &openapi3.Swagger{
|
||||
Info: info,
|
||||
}
|
||||
|
||||
swagger, err := generateNewValidSwagger(swagger)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, &openapi3.Swagger{
|
||||
OpenAPI: defaultOpenapiVersion,
|
||||
Info: info,
|
||||
Paths: openapi3.Paths{},
|
||||
}, swagger)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGenerateAndExposeSwagger(t *testing.T) {
|
||||
t.Run("fails swagger validation", func(t *testing.T) {
|
||||
mRouter := mux.NewRouter()
|
||||
router, err := New(mRouter, Options{
|
||||
Openapi: &openapi3.Swagger{
|
||||
Info: &openapi3.Info{
|
||||
Title: "title",
|
||||
Version: "version",
|
||||
},
|
||||
Components: openapi3.Components{
|
||||
Schemas: map[string]*openapi3.SchemaRef{
|
||||
"&%": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = router.GenerateAndExposeSwagger()
|
||||
require.Error(t, err)
|
||||
require.True(t, strings.HasPrefix(err.Error(), fmt.Sprintf("%s:", ErrValidatingSwagger)))
|
||||
})
|
||||
|
||||
t.Run("correctly expose json documentation from loaded swagger file", func(t *testing.T) {
|
||||
mRouter := mux.NewRouter()
|
||||
|
||||
swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromFile("testdata/users_employees.json")
|
||||
require.NoError(t, err)
|
||||
|
||||
router, err := New(mRouter, Options{
|
||||
Openapi: swagger,
|
||||
})
|
||||
|
||||
err = router.GenerateAndExposeSwagger()
|
||||
require.NoError(t, err)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest(http.MethodGet, "/hello", nil)
|
||||
|
||||
router.ServeHTTP(w, r)
|
||||
req := httptest.NewRequest(http.MethodGet, JSONDocumentationPath, nil)
|
||||
mRouter.ServeHTTP(w, req)
|
||||
|
||||
require.Equal(t, http.StatusOK, w.Result().StatusCode)
|
||||
require.True(t, strings.Contains(w.Result().Header.Get("content-type"), "application/json"))
|
||||
|
||||
body := readBody(t, w.Result().Body)
|
||||
require.Equal(t, "OK", body)
|
||||
|
||||
t.Run("and generate swagger", func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest(http.MethodGet, JSONDocumentationPath, nil)
|
||||
|
||||
router.ServeHTTP(w, r)
|
||||
|
||||
require.Equal(t, http.StatusOK, w.Result().StatusCode)
|
||||
|
||||
body := readBody(t, w.Result().Body)
|
||||
require.Equal(t, "{\"components\":{},\"info\":{\"title\":\"test swagger title\",\"version\":\"test swagger version\"},\"openapi\":\"3.0.0\",\"paths\":{\"/hello\":{\"get\":{\"responses\":{\"default\":{\"description\":\"\"}}}}}}", body)
|
||||
})
|
||||
actual, err := ioutil.ReadFile("testdata/users_employees.json")
|
||||
require.NoError(t, err)
|
||||
require.JSONEq(t, string(actual), body)
|
||||
})
|
||||
}
|
||||
|
||||
func readBody (t *testing.T, requestBody io.ReadCloser) string {
|
||||
t.Helper()
|
||||
|
||||
body, err := ioutil.ReadAll(requestBody)
|
||||
require.NoError(t, err)
|
||||
|
||||
return string(body)
|
||||
}
|
||||
|
||||
func setupSwagger(t *testing.T) *Router {
|
||||
t.Helper()
|
||||
|
||||
context := context.Background()
|
||||
r := mux.NewRouter()
|
||||
|
||||
router, err := New(r, RouterOptions{
|
||||
Context: context,
|
||||
Openapi: &openapi3.Swagger{
|
||||
Info: &openapi3.Info{
|
||||
Title: swaggerOpenapiTitle,
|
||||
Version: swaggerOpenapiVersion,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
handler := func(w http.ResponseWriter, req *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(`OK`))
|
||||
}
|
||||
operation := Operation{}
|
||||
|
||||
_, err = router.AddRoute(http.MethodGet, "/hello", handler, operation)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = router.GenerateAndExposeSwagger()
|
||||
require.NoError(t, err)
|
||||
|
||||
return router
|
||||
}
|
||||
|
||||
240
route.go
Normal file
240
route.go
Normal file
@@ -0,0 +1,240 @@
|
||||
package swagger
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
|
||||
"github.com/alecthomas/jsonschema"
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrResponses is thrown if error occurs generating responses schemas.
|
||||
ErrResponses = errors.New("errors generating responses schema")
|
||||
// ErrRequestBody is thrown if error occurs generating responses schemas.
|
||||
ErrRequestBody = errors.New("errors generating request body schema")
|
||||
// ErrPathParams is thrown if error occurs generating path params schemas.
|
||||
ErrPathParams = errors.New("errors generating path parameters schema")
|
||||
// ErrQuerystring is thrown if error occurs generating querystring params schemas.
|
||||
ErrQuerystring = errors.New("errors generating querystring schema")
|
||||
)
|
||||
|
||||
// Operation type
|
||||
type Operation struct {
|
||||
*openapi3.Operation
|
||||
}
|
||||
|
||||
// Handler is the http type handler
|
||||
type Handler func(w http.ResponseWriter, req *http.Request)
|
||||
|
||||
// AddRawRoute add route to router with specific method, path and handler. Add the
|
||||
// router also to the swagger schema, after validating it
|
||||
func (r Router) AddRawRoute(method string, path string, handler Handler, operation Operation) (*mux.Route, error) {
|
||||
if operation.Operation != nil {
|
||||
err := operation.Validate(r.context)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
operation.Operation = openapi3.NewOperation()
|
||||
operation.Responses = openapi3.NewResponses()
|
||||
}
|
||||
r.swaggerSchema.AddOperation(path, method, operation.Operation)
|
||||
|
||||
return r.router.HandleFunc(path, func(w http.ResponseWriter, req *http.Request) {
|
||||
// Handle, when content-type is json, the request/response marshalling? Maybe with a specific option.
|
||||
handler(w, req)
|
||||
}).Methods(method), nil
|
||||
}
|
||||
|
||||
// SchemaValue is the struct containing the schema information.
|
||||
type SchemaValue struct {
|
||||
Content interface{}
|
||||
Description string
|
||||
|
||||
// ContentType is to be used only with RequestBody. Valid ContentType
|
||||
// are application/json or multipart/form-data.
|
||||
ContentType string
|
||||
AllowAdditionalProperties bool
|
||||
}
|
||||
|
||||
// Schema of the route.
|
||||
type Schema struct {
|
||||
PathParams map[string]SchemaValue
|
||||
Querystring map[string]SchemaValue
|
||||
Headers map[string]SchemaValue
|
||||
Cookies map[string]SchemaValue
|
||||
RequestBody *SchemaValue
|
||||
Responses map[int]SchemaValue
|
||||
}
|
||||
|
||||
const (
|
||||
pathParamsType = "path"
|
||||
queryParamType = "query"
|
||||
headerParamType = "header"
|
||||
cookieParamType = "cookie"
|
||||
)
|
||||
|
||||
// AddRoute add a route with json schema inferted by passed schema.
|
||||
func (r Router) AddRoute(method string, path string, handler Handler, schema Schema) (*mux.Route, error) {
|
||||
operation := openapi3.NewOperation()
|
||||
operation.Responses = make(openapi3.Responses)
|
||||
|
||||
err := r.resolveRequestBodySchema(schema.RequestBody, operation)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %s", ErrRequestBody, err)
|
||||
}
|
||||
|
||||
err = r.resolveResponsesSchema(schema.Responses, operation)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %s", ErrResponses, err)
|
||||
}
|
||||
|
||||
err = r.resolveParameterSchema(pathParamsType, schema.PathParams, operation)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %s", ErrPathParams, err)
|
||||
}
|
||||
|
||||
err = r.resolveParameterSchema(queryParamType, schema.Querystring, operation)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %s", ErrPathParams, err)
|
||||
}
|
||||
|
||||
err = r.resolveParameterSchema(headerParamType, schema.Headers, operation)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %s", ErrPathParams, err)
|
||||
}
|
||||
|
||||
err = r.resolveParameterSchema(cookieParamType, schema.Cookies, operation)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %s", ErrPathParams, err)
|
||||
}
|
||||
|
||||
return r.AddRawRoute(method, path, handler, Operation{operation})
|
||||
}
|
||||
|
||||
func (r Router) getSchemaFromInterface(v interface{}, allowAdditionalProperties bool) (*openapi3.Schema, error) {
|
||||
if v == nil {
|
||||
return &openapi3.Schema{}, nil
|
||||
}
|
||||
|
||||
reflector := &jsonschema.Reflector{
|
||||
DoNotReference: true,
|
||||
AllowAdditionalProperties: allowAdditionalProperties,
|
||||
}
|
||||
|
||||
jsonSchema := reflector.Reflect(v)
|
||||
jsonschema.Version = ""
|
||||
// Empty definitions. Definitions are not valid in openapi3, which use components.
|
||||
// In the future, we could add an option to fill the components in openapi spec.
|
||||
jsonSchema.Definitions = nil
|
||||
|
||||
data, err := jsonSchema.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
schema := openapi3.NewSchema()
|
||||
err = schema.UnmarshalJSON(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return schema, nil
|
||||
}
|
||||
|
||||
func (r Router) resolveRequestBodySchema(bodySchema *SchemaValue, operation *openapi3.Operation) error {
|
||||
if bodySchema == nil {
|
||||
return nil
|
||||
}
|
||||
requestBodySchema, err := r.getSchemaFromInterface(bodySchema.Content, bodySchema.AllowAdditionalProperties)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
requestBody := openapi3.NewRequestBody()
|
||||
switch bodySchema.ContentType {
|
||||
case "multipart/form-data":
|
||||
requestBody = requestBody.WithFormDataSchema(requestBodySchema)
|
||||
case "application/json", "":
|
||||
requestBody = requestBody.WithJSONSchema(requestBodySchema)
|
||||
default:
|
||||
return fmt.Errorf("invalid content-type in request body")
|
||||
}
|
||||
|
||||
if bodySchema.Description != "" {
|
||||
requestBody.WithDescription(bodySchema.Description)
|
||||
}
|
||||
|
||||
operation.RequestBody = &openapi3.RequestBodyRef{
|
||||
Value: requestBody,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r Router) resolveResponsesSchema(responses map[int]SchemaValue, operation *openapi3.Operation) error {
|
||||
if responses == nil {
|
||||
operation.Responses = openapi3.NewResponses()
|
||||
}
|
||||
for statusCode, v := range responses {
|
||||
response := openapi3.NewResponse()
|
||||
|
||||
responseSchema, err := r.getSchemaFromInterface(v.Content, v.AllowAdditionalProperties)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
response = response.WithDescription(v.Description)
|
||||
response = response.WithJSONSchema(responseSchema)
|
||||
|
||||
operation.AddResponse(statusCode, response)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r Router) resolveParameterSchema(paramType string, paramConfig map[string]SchemaValue, operation *openapi3.Operation) error {
|
||||
var keys = make([]string, 0, len(paramConfig))
|
||||
for k := range paramConfig {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
for _, key := range keys {
|
||||
v := paramConfig[key]
|
||||
var param *openapi3.Parameter
|
||||
switch paramType {
|
||||
case pathParamsType:
|
||||
param = openapi3.NewPathParameter(key)
|
||||
case queryParamType:
|
||||
param = openapi3.NewQueryParameter(key)
|
||||
case headerParamType:
|
||||
param = openapi3.NewHeaderParameter(key)
|
||||
case cookieParamType:
|
||||
param = openapi3.NewCookieParameter(key)
|
||||
default:
|
||||
return fmt.Errorf("invalid param type")
|
||||
}
|
||||
|
||||
schema := openapi3.NewSchema()
|
||||
if v.Content != nil {
|
||||
var err error
|
||||
schema, err = r.getSchemaFromInterface(v.Content, v.AllowAdditionalProperties)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
param = param.WithSchema(schema)
|
||||
|
||||
if v.Description != "" {
|
||||
param = param.WithDescription(v.Description)
|
||||
}
|
||||
|
||||
operation.AddParameter(param)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
723
route_test.go
Normal file
723
route_test.go
Normal file
@@ -0,0 +1,723 @@
|
||||
package swagger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAddRoute(t *testing.T) {
|
||||
type User struct {
|
||||
Name string `json:"name" jsonschema:"title=The user name,required" jsonschema_extras:"example=Jane"`
|
||||
PhoneNumber int `json:"phone" jsonschema:"title=mobile number of user"`
|
||||
Groups []string `json:"groups,omitempty" jsonschema:"title=groups of the user,default=users"`
|
||||
Address string `json:"address" jsonschema:"title=user address"`
|
||||
}
|
||||
type Users []User
|
||||
type errorResponse struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type Employees struct {
|
||||
OrganizationName string `json:"organization_name"`
|
||||
Users Users `json:"users" jsonschema:"selected users"`
|
||||
}
|
||||
type FormData struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Address struct {
|
||||
Street string `json:"street,omitempty"`
|
||||
City string `json:"city,omitempty"`
|
||||
} `json:"address,omitempty"`
|
||||
ProfileImage string `json:"profileImage,omitempty" jsonschema_extras:"format=binary"`
|
||||
}
|
||||
|
||||
okHandler := func(w http.ResponseWriter, req *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("OK"))
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
routes func(t *testing.T, router *Router)
|
||||
fixturesPath string
|
||||
testPath string
|
||||
testMethod string
|
||||
}{
|
||||
{
|
||||
name: "no routes",
|
||||
routes: func(t *testing.T, router *Router) {},
|
||||
fixturesPath: "testdata/empty.json",
|
||||
},
|
||||
{
|
||||
name: "empty route schema",
|
||||
routes: func(t *testing.T, router *Router) {
|
||||
_, err := router.AddRoute(http.MethodPost, "/", okHandler, Schema{})
|
||||
require.NoError(t, err)
|
||||
},
|
||||
testPath: "/",
|
||||
testMethod: http.MethodPost,
|
||||
fixturesPath: "testdata/empty-route-schema.json",
|
||||
},
|
||||
{
|
||||
name: "multiple real routes",
|
||||
routes: func(t *testing.T, router *Router) {
|
||||
_, err := router.AddRoute(http.MethodPost, "/users", okHandler, Schema{
|
||||
RequestBody: &SchemaValue{
|
||||
Content: User{},
|
||||
},
|
||||
Responses: map[int]SchemaValue{
|
||||
201: {
|
||||
Content: "",
|
||||
},
|
||||
401: {
|
||||
Content: &errorResponse{},
|
||||
Description: "invalid request",
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = router.AddRoute(http.MethodGet, "/users", okHandler, Schema{
|
||||
Responses: map[int]SchemaValue{
|
||||
200: {
|
||||
Content: &Users{},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = router.AddRoute(http.MethodGet, "/employees", okHandler, Schema{
|
||||
Responses: map[int]SchemaValue{
|
||||
200: {
|
||||
Content: &Employees{},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
},
|
||||
testPath: "/users",
|
||||
fixturesPath: "testdata/users_employees.json",
|
||||
},
|
||||
{
|
||||
name: "multipart request body",
|
||||
routes: func(t *testing.T, router *Router) {
|
||||
_, err := router.AddRoute(http.MethodPost, "/files", okHandler, Schema{
|
||||
RequestBody: &SchemaValue{
|
||||
Content: &FormData{},
|
||||
Description: "upload file",
|
||||
ContentType: "multipart/form-data",
|
||||
AllowAdditionalProperties: true,
|
||||
},
|
||||
Responses: map[int]SchemaValue{
|
||||
200: {Content: ""},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
},
|
||||
testPath: "/files",
|
||||
testMethod: http.MethodPost,
|
||||
fixturesPath: "testdata/multipart-requestbody.json",
|
||||
},
|
||||
{
|
||||
name: "schema with params",
|
||||
routes: func(t *testing.T, router *Router) {
|
||||
var number = 0
|
||||
_, err := router.AddRoute(http.MethodGet, "/users/{userId}", okHandler, Schema{
|
||||
PathParams: map[string]SchemaValue{
|
||||
"userId": {
|
||||
Content: number,
|
||||
Description: "userId is a number above 0",
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = router.AddRoute(http.MethodGet, "/cars/{carId}/drivers/{driverId}", okHandler, Schema{
|
||||
PathParams: map[string]SchemaValue{
|
||||
"carId": {
|
||||
Content: "",
|
||||
},
|
||||
"driverId": {
|
||||
Content: "",
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
},
|
||||
testPath: "/users/12",
|
||||
fixturesPath: "testdata/params.json",
|
||||
},
|
||||
{
|
||||
name: "schema with querystring",
|
||||
routes: func(t *testing.T, router *Router) {
|
||||
_, err := router.AddRoute(http.MethodGet, "/projects", okHandler, Schema{
|
||||
Querystring: map[string]SchemaValue{
|
||||
"projectId": {
|
||||
Content: "",
|
||||
Description: "projectId is the project id",
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
},
|
||||
testPath: "/projects",
|
||||
fixturesPath: "testdata/query.json",
|
||||
},
|
||||
{
|
||||
name: "schema with headers",
|
||||
routes: func(t *testing.T, router *Router) {
|
||||
_, err := router.AddRoute(http.MethodGet, "/projects", okHandler, Schema{
|
||||
Headers: map[string]SchemaValue{
|
||||
"foo": {
|
||||
Content: "",
|
||||
Description: "foo description",
|
||||
},
|
||||
"bar": {
|
||||
Content: "",
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
},
|
||||
testPath: "/projects",
|
||||
fixturesPath: "testdata/headers.json",
|
||||
},
|
||||
{
|
||||
name: "schema with cookies",
|
||||
routes: func(t *testing.T, router *Router) {
|
||||
_, err := router.AddRoute(http.MethodGet, "/projects", okHandler, Schema{
|
||||
Cookies: map[string]SchemaValue{
|
||||
"debug": {
|
||||
Content: 0,
|
||||
Description: "boolean. Set 0 to disable and 1 to enable",
|
||||
},
|
||||
"csrftoken": {
|
||||
Content: "",
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
},
|
||||
testPath: "/projects",
|
||||
fixturesPath: "testdata/cookies.json",
|
||||
},
|
||||
{
|
||||
name: "schema defined without content",
|
||||
routes: func(t *testing.T, router *Router) {
|
||||
_, err := router.AddRoute(http.MethodPost, "/{id}", okHandler, Schema{
|
||||
RequestBody: &SchemaValue{
|
||||
Description: "request body without schema",
|
||||
},
|
||||
Responses: map[int]SchemaValue{
|
||||
204: {},
|
||||
},
|
||||
PathParams: map[string]SchemaValue{
|
||||
"id": {},
|
||||
},
|
||||
Querystring: map[string]SchemaValue{
|
||||
"q": {},
|
||||
},
|
||||
Headers: map[string]SchemaValue{
|
||||
"key": {},
|
||||
},
|
||||
Cookies: map[string]SchemaValue{
|
||||
"cookie1": {},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
},
|
||||
testPath: "/foobar",
|
||||
testMethod: http.MethodPost,
|
||||
fixturesPath: "testdata/schema-no-content.json",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
context := context.Background()
|
||||
r := mux.NewRouter()
|
||||
|
||||
router, err := New(r, Options{
|
||||
Context: context,
|
||||
Openapi: getBaseSwagger(t),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, router)
|
||||
|
||||
// Add routes to test
|
||||
test.routes(t, router)
|
||||
|
||||
err = router.GenerateAndExposeSwagger()
|
||||
require.NoError(t, err)
|
||||
|
||||
if test.testPath != "" {
|
||||
if test.testMethod == "" {
|
||||
test.testMethod = http.MethodGet
|
||||
}
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(test.testMethod, test.testPath, nil)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
require.Equal(t, http.StatusOK, w.Result().StatusCode)
|
||||
|
||||
body := readBody(t, w.Result().Body)
|
||||
require.Equal(t, "OK", body)
|
||||
}
|
||||
|
||||
t.Run("and generate swagger documentation in json", func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, JSONDocumentationPath, nil)
|
||||
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
require.Equal(t, http.StatusOK, w.Result().StatusCode)
|
||||
|
||||
body := readBody(t, w.Result().Body)
|
||||
actual, err := ioutil.ReadFile(test.fixturesPath)
|
||||
require.NoError(t, err)
|
||||
require.JSONEq(t, string(actual), body, "actual json data: ", string(actual))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("", func(t *testing.T) {
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestResolveRequestBodySchema(t *testing.T) {
|
||||
type TestStruct struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
bodySchema *SchemaValue
|
||||
expectedErr error
|
||||
expectedJSON string
|
||||
}{
|
||||
{
|
||||
name: "empty body schema",
|
||||
expectedErr: nil,
|
||||
expectedJSON: `{"responses": null}`,
|
||||
},
|
||||
{
|
||||
name: "schema multipart",
|
||||
expectedErr: nil,
|
||||
bodySchema: &SchemaValue{
|
||||
Content: &TestStruct{},
|
||||
ContentType: "multipart/form-data",
|
||||
},
|
||||
expectedJSON: `{
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"multipart/form-data": {
|
||||
"schema": {
|
||||
"type":"object",
|
||||
"additionalProperties":false,
|
||||
"properties": {
|
||||
"id": {"type":"string"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": null
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "content-type application/json",
|
||||
expectedErr: nil,
|
||||
bodySchema: &SchemaValue{
|
||||
Content: &TestStruct{},
|
||||
ContentType: "application/json",
|
||||
},
|
||||
expectedJSON: `{
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type":"object",
|
||||
"additionalProperties":false,
|
||||
"properties": {
|
||||
"id": {"type":"string"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": null
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "no content-type - default to json",
|
||||
expectedErr: nil,
|
||||
bodySchema: &SchemaValue{
|
||||
Content: &TestStruct{},
|
||||
},
|
||||
expectedJSON: `{
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type":"object",
|
||||
"additionalProperties":false,
|
||||
"properties": {
|
||||
"id": {"type":"string"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": null
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "with description",
|
||||
expectedErr: nil,
|
||||
bodySchema: &SchemaValue{
|
||||
Content: &TestStruct{},
|
||||
Description: "my custom description",
|
||||
},
|
||||
expectedJSON: `{
|
||||
"requestBody": {
|
||||
"description": "my custom description",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type":"object",
|
||||
"additionalProperties":false,
|
||||
"properties": {
|
||||
"id": {"type":"string"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": null
|
||||
}`,
|
||||
},
|
||||
// FIXME: this test case exhibits a wrong behavior. It should be supported.
|
||||
{
|
||||
name: "content type text/plain",
|
||||
expectedErr: fmt.Errorf("invalid content-type in request body"),
|
||||
bodySchema: &SchemaValue{
|
||||
Content: &TestStruct{},
|
||||
ContentType: "text/plain",
|
||||
},
|
||||
},
|
||||
// FIXME: this test case exhibits a wrong behavior. It should be supported.
|
||||
{
|
||||
name: "generic content type - it represent all types",
|
||||
expectedErr: fmt.Errorf("invalid content-type in request body"),
|
||||
bodySchema: &SchemaValue{
|
||||
Content: &TestStruct{},
|
||||
ContentType: "*/*",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
mux := mux.NewRouter()
|
||||
router, err := New(mux, Options{
|
||||
Openapi: getBaseSwagger(t),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
operation := openapi3.NewOperation()
|
||||
|
||||
err := router.resolveRequestBodySchema(test.bodySchema, operation)
|
||||
|
||||
if err == nil {
|
||||
data, _ := operation.MarshalJSON()
|
||||
jsonData := string(data)
|
||||
require.JSONEq(t, test.expectedJSON, jsonData, "actual json data: ", jsonData)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
require.Equal(t, test.expectedErr, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveResponsesSchema(t *testing.T) {
|
||||
type TestStruct struct {
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
responsesSchema map[int]SchemaValue
|
||||
expectedErr error
|
||||
expectedJSON string
|
||||
}{
|
||||
{
|
||||
name: "empty responses schema",
|
||||
expectedErr: nil,
|
||||
expectedJSON: `{"responses": {"default":{"description":""}}}`,
|
||||
},
|
||||
{
|
||||
name: "with 1 status code",
|
||||
responsesSchema: map[int]SchemaValue{
|
||||
200: {
|
||||
Content: &TestStruct{},
|
||||
},
|
||||
},
|
||||
expectedErr: nil,
|
||||
expectedJSON: `{
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "with more status codes",
|
||||
responsesSchema: map[int]SchemaValue{
|
||||
200: {
|
||||
Content: &TestStruct{},
|
||||
},
|
||||
400: {
|
||||
Content: "",
|
||||
},
|
||||
},
|
||||
expectedErr: nil,
|
||||
expectedJSON: `{
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "with custom description",
|
||||
responsesSchema: map[int]SchemaValue{
|
||||
400: {
|
||||
Content: "",
|
||||
Description: "a description",
|
||||
},
|
||||
},
|
||||
expectedErr: nil,
|
||||
expectedJSON: `{
|
||||
"responses": {
|
||||
"400": {
|
||||
"description": "a description",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
},
|
||||
}
|
||||
|
||||
mux := mux.NewRouter()
|
||||
router, err := New(mux, Options{
|
||||
Openapi: getBaseSwagger(t),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
operation := openapi3.NewOperation()
|
||||
operation.Responses = make(openapi3.Responses)
|
||||
|
||||
err := router.resolveResponsesSchema(test.responsesSchema, operation)
|
||||
|
||||
if err == nil {
|
||||
data, _ := operation.MarshalJSON()
|
||||
jsonData := string(data)
|
||||
require.JSONEq(t, test.expectedJSON, jsonData, "actual json data: ", jsonData)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
require.Equal(t, test.expectedErr, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveParametersSchema(t *testing.T) {
|
||||
type TestStruct struct {
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
paramsSchema map[string]SchemaValue
|
||||
paramType string
|
||||
expectedErr error
|
||||
expectedJSON string
|
||||
}{
|
||||
{
|
||||
name: "empty responses schema",
|
||||
paramType: pathParamsType,
|
||||
expectedJSON: `{"responses": null}`,
|
||||
},
|
||||
{
|
||||
name: "path param",
|
||||
paramType: pathParamsType,
|
||||
paramsSchema: map[string]SchemaValue{
|
||||
"foo": {
|
||||
Content: "",
|
||||
},
|
||||
},
|
||||
expectedJSON: `{
|
||||
"parameters": [{
|
||||
"in": "path",
|
||||
"name": "foo",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}],
|
||||
"responses": null
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "query param",
|
||||
paramType: queryParamType,
|
||||
paramsSchema: map[string]SchemaValue{
|
||||
"foo": {
|
||||
Content: "",
|
||||
},
|
||||
},
|
||||
expectedJSON: `{
|
||||
"parameters": [{
|
||||
"in": "query",
|
||||
"name": "foo",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}],
|
||||
"responses": null
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "cookie param",
|
||||
paramType: cookieParamType,
|
||||
paramsSchema: map[string]SchemaValue{
|
||||
"foo": {
|
||||
Content: "",
|
||||
},
|
||||
},
|
||||
expectedJSON: `{
|
||||
"parameters": [{
|
||||
"in": "cookie",
|
||||
"name": "foo",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}],
|
||||
"responses": null
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "header param",
|
||||
paramType: headerParamType,
|
||||
paramsSchema: map[string]SchemaValue{
|
||||
"foo": {
|
||||
Content: "",
|
||||
},
|
||||
},
|
||||
expectedJSON: `{
|
||||
"parameters": [{
|
||||
"in": "header",
|
||||
"name": "foo",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}],
|
||||
"responses": null
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "wrong param type",
|
||||
paramType: "wrong",
|
||||
paramsSchema: map[string]SchemaValue{
|
||||
"foo": {
|
||||
Content: "",
|
||||
},
|
||||
},
|
||||
expectedErr: fmt.Errorf("invalid param type"),
|
||||
},
|
||||
}
|
||||
|
||||
mux := mux.NewRouter()
|
||||
router, err := New(mux, Options{
|
||||
Openapi: getBaseSwagger(t),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
operation := openapi3.NewOperation()
|
||||
|
||||
err := router.resolveParameterSchema(test.paramType, test.paramsSchema, operation)
|
||||
|
||||
if err == nil {
|
||||
data, _ := operation.MarshalJSON()
|
||||
jsonData := string(data)
|
||||
require.JSONEq(t, test.expectedJSON, jsonData, "actual json data: ", jsonData)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
require.Equal(t, test.expectedErr, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func getBaseSwagger(t *testing.T) *openapi3.Swagger {
|
||||
t.Helper()
|
||||
|
||||
return &openapi3.Swagger{
|
||||
Info: &openapi3.Info{
|
||||
Title: swaggerOpenapiTitle,
|
||||
Version: swaggerOpenapiVersion,
|
||||
},
|
||||
}
|
||||
}
|
||||
36
testdata/cookies.json
vendored
Normal file
36
testdata/cookies.json
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"components": {},
|
||||
"info": {
|
||||
"title": "test swagger title",
|
||||
"version": "test swagger version"
|
||||
},
|
||||
"openapi": "3.0.0",
|
||||
"paths": {
|
||||
"/projects": {
|
||||
"get": {
|
||||
"parameters": [
|
||||
{
|
||||
"in": "cookie",
|
||||
"name": "csrftoken",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "boolean. Set 0 to disable and 1 to enable",
|
||||
"in": "cookie",
|
||||
"name": "debug",
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"default": {
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
19
testdata/empty-route-schema.json
vendored
Normal file
19
testdata/empty-route-schema.json
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"components": {},
|
||||
"info": {
|
||||
"title": "test swagger title",
|
||||
"version": "test swagger version"
|
||||
},
|
||||
"openapi": "3.0.0",
|
||||
"paths": {
|
||||
"/": {
|
||||
"post": {
|
||||
"responses": {
|
||||
"default": {
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
9
testdata/empty.json
vendored
Normal file
9
testdata/empty.json
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"components": {},
|
||||
"info": {
|
||||
"title": "test swagger title",
|
||||
"version": "test swagger version"
|
||||
},
|
||||
"openapi": "3.0.0",
|
||||
"paths": {}
|
||||
}
|
||||
36
testdata/headers.json
vendored
Normal file
36
testdata/headers.json
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"components": {},
|
||||
"info": {
|
||||
"title": "test swagger title",
|
||||
"version": "test swagger version"
|
||||
},
|
||||
"openapi": "3.0.0",
|
||||
"paths": {
|
||||
"/projects": {
|
||||
"get": {
|
||||
"parameters": [
|
||||
{
|
||||
"in": "header",
|
||||
"name": "bar",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "foo description",
|
||||
"in": "header",
|
||||
"name": "foo",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"default": {
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
58
testdata/multipart-requestbody.json
vendored
Normal file
58
testdata/multipart-requestbody.json
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"components": {},
|
||||
"info": {
|
||||
"title": "test swagger title",
|
||||
"version": "test swagger version"
|
||||
},
|
||||
"openapi": "3.0.0",
|
||||
"paths": {
|
||||
"/files": {
|
||||
"post": {
|
||||
"requestBody": {
|
||||
"description": "upload file",
|
||||
"content": {
|
||||
"multipart/form-data": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"address": {
|
||||
"additionalProperties": true,
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"street": {
|
||||
"type": "string"
|
||||
},
|
||||
"city": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"profileImage": {
|
||||
"type": "string",
|
||||
"format": "binary"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
57
testdata/params.json
vendored
Normal file
57
testdata/params.json
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"components": {},
|
||||
"info": {
|
||||
"title": "test swagger title",
|
||||
"version": "test swagger version"
|
||||
},
|
||||
"openapi": "3.0.0",
|
||||
"paths": {
|
||||
"/cars/{carId}/drivers/{driverId}": {
|
||||
"get": {
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "carId",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "path",
|
||||
"name": "driverId",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"default": {
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/users/{userId}": {
|
||||
"get": {
|
||||
"parameters": [
|
||||
{
|
||||
"description": "userId is a number above 0",
|
||||
"in": "path",
|
||||
"name": "userId",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"default": {
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
29
testdata/query.json
vendored
Normal file
29
testdata/query.json
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"components": {},
|
||||
"info": {
|
||||
"title": "test swagger title",
|
||||
"version": "test swagger version"
|
||||
},
|
||||
"openapi": "3.0.0",
|
||||
"paths": {
|
||||
"/projects": {
|
||||
"get": {
|
||||
"parameters": [
|
||||
{
|
||||
"description": "projectId is the project id",
|
||||
"in": "query",
|
||||
"name": "projectId",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"default": {
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
55
testdata/schema-no-content.json
vendored
Normal file
55
testdata/schema-no-content.json
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"components": {},
|
||||
"info": {
|
||||
"title": "test swagger title",
|
||||
"version": "test swagger version"
|
||||
},
|
||||
"openapi": "3.0.0",
|
||||
"paths": {
|
||||
"/{id}": {
|
||||
"post": {
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "id",
|
||||
"required": true,
|
||||
"schema": {}
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "q",
|
||||
"schema": {}
|
||||
},
|
||||
{
|
||||
"in": "header",
|
||||
"name": "key",
|
||||
"schema": {}
|
||||
},
|
||||
{
|
||||
"in": "cookie",
|
||||
"name": "cookie1",
|
||||
"schema": {}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {}
|
||||
}
|
||||
},
|
||||
"description": "request body without schema"
|
||||
},
|
||||
"responses": {
|
||||
"204": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {}
|
||||
}
|
||||
},
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
196
testdata/users_employees.json
vendored
Normal file
196
testdata/users_employees.json
vendored
Normal file
@@ -0,0 +1,196 @@
|
||||
{
|
||||
"components": {},
|
||||
"info": {
|
||||
"title": "test swagger title",
|
||||
"version": "test swagger version"
|
||||
},
|
||||
"openapi": "3.0.0",
|
||||
"paths": {
|
||||
"/employees": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"organization_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"users": {
|
||||
"items": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"address": {
|
||||
"title": "user address",
|
||||
"type": "string"
|
||||
},
|
||||
"groups": {
|
||||
"default": [
|
||||
"users"
|
||||
],
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": "groups of the user",
|
||||
"type": "array"
|
||||
},
|
||||
"name": {
|
||||
"example": "Jane",
|
||||
"title": "The user name",
|
||||
"type": "string"
|
||||
},
|
||||
"phone": {
|
||||
"title": "mobile number of user",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"phone",
|
||||
"address"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"organization_name",
|
||||
"users"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/users": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"items": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"address": {
|
||||
"title": "user address",
|
||||
"type": "string"
|
||||
},
|
||||
"groups": {
|
||||
"default": [
|
||||
"users"
|
||||
],
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": "groups of the user",
|
||||
"type": "array"
|
||||
},
|
||||
"name": {
|
||||
"example": "Jane",
|
||||
"title": "The user name",
|
||||
"type": "string"
|
||||
},
|
||||
"phone": {
|
||||
"title": "mobile number of user",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"phone",
|
||||
"address"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"address": {
|
||||
"title": "user address",
|
||||
"type": "string"
|
||||
},
|
||||
"groups": {
|
||||
"default": [
|
||||
"users"
|
||||
],
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": "groups of the user",
|
||||
"type": "array"
|
||||
},
|
||||
"name": {
|
||||
"example": "Jane",
|
||||
"title": "The user name",
|
||||
"type": "string"
|
||||
},
|
||||
"phone": {
|
||||
"title": "mobile number of user",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"phone",
|
||||
"address"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": ""
|
||||
},
|
||||
"401": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"message"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "invalid request"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user