mirror of
https://github.com/davidebianchi/gswagger.git
synced 2025-12-23 23:38:43 -05:00
Compare commits
178 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef1ac5fd7e | ||
|
|
fc597bec00 | ||
|
|
eb3c4d9c98 | ||
|
|
3563d41c0f | ||
|
|
1802f29ad3 | ||
|
|
56651d1029 | ||
|
|
91f50dd0e4 | ||
|
|
a98a205453 | ||
|
|
3720da7606 | ||
|
|
fb94293412 | ||
|
|
d1f6294d95 | ||
|
|
a828758065 | ||
|
|
09e3bd484e | ||
|
|
617c44900c | ||
|
|
1cd88c70bb | ||
|
|
d3bbc528ac | ||
|
|
91c15b79b1 | ||
|
|
bd884cb1de | ||
|
|
b776ccf233 | ||
|
|
0d75c2cb5e | ||
|
|
2354daa4a9 | ||
|
|
8d05c61745 | ||
|
|
9a6b0b9b16 | ||
|
|
dd94c5cd25 | ||
|
|
8047e54eac | ||
|
|
e6adc34cee | ||
|
|
2b87521135 | ||
|
|
d095403a23 | ||
|
|
526265eda9 | ||
|
|
88bbf2f41c | ||
|
|
6a1bd1cd19 | ||
|
|
c44efeed6e | ||
|
|
4b06c26421 | ||
|
|
7a48480545 | ||
|
|
bbc469d26d | ||
|
|
be8a1de244 | ||
|
|
39f8f916d1 | ||
|
|
9fac1c3a4f | ||
|
|
d5e740ecac | ||
|
|
5f24f99c89 | ||
|
|
19e6e6e888 | ||
|
|
449ee873f8 | ||
|
|
8d08ab4e88 | ||
|
|
e4154d1870 | ||
|
|
34f6bc882e | ||
|
|
bb30bcbb18 | ||
|
|
e3bf8ad397 | ||
|
|
36c6f8b159 | ||
|
|
4cb82ead72 | ||
|
|
5851dbec22 | ||
|
|
7eabb18dd1 | ||
|
|
b50cc86f43 | ||
|
|
8330d20f23 | ||
|
|
68cc1ddbf1 | ||
|
|
5f67ad2aad | ||
|
|
0676ab69a8 | ||
|
|
838f862067 | ||
|
|
48371417ce | ||
|
|
3e11724cd9 | ||
|
|
85a65a69aa | ||
|
|
0b3a79b0b5 | ||
|
|
83cb8c6301 | ||
|
|
a605011038 | ||
|
|
4f18946fab | ||
|
|
8ea5444777 | ||
|
|
e6e53be7d8 | ||
|
|
a503dd54db | ||
|
|
231f0b5b65 | ||
|
|
6d81ef8469 | ||
|
|
f6c98d97cc | ||
|
|
4587271bc2 | ||
|
|
17f9235abd | ||
|
|
e81a74c306 | ||
|
|
48e22b5674 | ||
|
|
a1df7c966e | ||
|
|
6e38c8b9ea | ||
|
|
40ea80b890 | ||
|
|
1daad04aab | ||
|
|
77e080c587 | ||
|
|
00d9267cf8 | ||
|
|
efab680870 | ||
|
|
fa4fd37f17 | ||
|
|
74baec392e | ||
|
|
a48fb550df | ||
|
|
76d2e514e1 | ||
|
|
aeb5680612 | ||
|
|
10a5f64f5c | ||
|
|
fca6b6910d | ||
|
|
da751837df | ||
|
|
cbe3488c8d | ||
|
|
315cd103a7 | ||
|
|
d7b5134200 | ||
|
|
31e40cedfd | ||
|
|
6f3dcc578c | ||
|
|
45cf385dec | ||
|
|
e5f3c438b4 | ||
|
|
047317ef17 | ||
|
|
b007e57092 | ||
|
|
6c9d0e579f | ||
|
|
f84327d424 | ||
|
|
3a598c0726 | ||
|
|
085abe98d1 | ||
|
|
c5fff8f982 | ||
|
|
fee2444c96 | ||
|
|
24dfb293ea | ||
|
|
a5b9bd3dc4 | ||
|
|
7f7c92eaca | ||
|
|
2939f1027d | ||
|
|
3a4118844e | ||
|
|
3e72099700 | ||
|
|
9fac38a162 | ||
|
|
424779a7d8 | ||
|
|
d6a528ea28 | ||
|
|
909c58608a | ||
|
|
43c6e37a47 | ||
|
|
b9dc8ff83c | ||
|
|
1b95853d11 | ||
|
|
e8369933fc | ||
|
|
89d668771a | ||
|
|
0039a3f74f | ||
|
|
b29887d15f | ||
|
|
e03aeb51c2 | ||
|
|
a3cb8e3775 | ||
|
|
995c3d97a2 | ||
|
|
d376b5411e | ||
|
|
b187bd716f | ||
|
|
d07b9c96f5 | ||
|
|
3c8cffc622 | ||
|
|
c5e3219f7a | ||
|
|
d9c105585b | ||
|
|
3f7d09cc36 | ||
|
|
57842cf39d | ||
|
|
3e359451c8 | ||
|
|
7896d8efd3 | ||
|
|
ac9ae09ff9 | ||
|
|
42bfc9c9a6 | ||
|
|
32233b0a6b | ||
|
|
ab052edbe2 | ||
|
|
90b7305b57 | ||
|
|
0a6ddb22c3 | ||
|
|
e884893fd5 | ||
|
|
1aaf503686 | ||
|
|
f109edd60a | ||
|
|
d187894847 | ||
|
|
6eeb5ab409 | ||
|
|
8efc400a2f | ||
|
|
5c5d02c68e | ||
|
|
e5b47a772a | ||
|
|
fa6f33399b | ||
|
|
786a5ed67d | ||
|
|
8f8ca16724 | ||
|
|
e1e3e95d80 | ||
|
|
1f550922b6 | ||
|
|
55cfa361ce | ||
|
|
0c867d2839 | ||
|
|
68fdf59cdb | ||
|
|
ebb8822041 | ||
|
|
491f683807 | ||
|
|
122be38154 | ||
|
|
a0ea89ed92 | ||
|
|
f21942de1b | ||
|
|
b1b7025862 | ||
|
|
3d21dece34 | ||
|
|
715c554482 | ||
|
|
d35432abaf | ||
|
|
ffb0e6b3f7 | ||
|
|
94abd8f4bf | ||
|
|
7232d5d823 | ||
|
|
b26ddbeecf | ||
|
|
3374e2bd80 | ||
|
|
33d81e9cdd | ||
|
|
a7a671be5a | ||
|
|
239a70a1a0 | ||
|
|
8c0aacb68c | ||
|
|
d145c6ca5d | ||
|
|
a8c5be8870 | ||
|
|
8fda85d9d3 | ||
|
|
a872dd02b7 |
66
.github/workflows/test-builds.yml
vendored
66
.github/workflows/test-builds.yml
vendored
@@ -5,28 +5,46 @@ on:
|
||||
push:
|
||||
jobs:
|
||||
tests:
|
||||
name: Test on go ${{ matrix.go_version }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
go_version: [1.15]
|
||||
os: [macos-latest, ubuntu-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Use golang ${{ matrix.go_version }}
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: ${{ matrix.go_version }}
|
||||
name: Test on go ${{ matrix.go_version }} os ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
go_version: ['1.24', '1.25']
|
||||
os: [ubuntu-latest]
|
||||
include:
|
||||
- go_version: '1.25'
|
||||
os: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Use golang ${{ matrix.go_version }}
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: ${{ matrix.go_version }}
|
||||
|
||||
- name: Go version
|
||||
run: |
|
||||
go version
|
||||
- name: Go get dependencies
|
||||
run: |
|
||||
go get -v -t -d ./...
|
||||
- name: Run tests
|
||||
run: |
|
||||
go test ./... -count=1 -race -cover
|
||||
- name: Build
|
||||
run: |
|
||||
go build -v .
|
||||
- name: Go version
|
||||
run: |
|
||||
go version
|
||||
- name: Go get dependencies
|
||||
run: |
|
||||
go get -v -t -d ./...
|
||||
- name: Run tests
|
||||
run: |
|
||||
go test ./... -count=1 -race -cover -coverprofile cover.out
|
||||
- name: Build
|
||||
run: |
|
||||
go build -v .
|
||||
- name: Send the coverage output
|
||||
uses: shogo82148/actions-goveralls@v1
|
||||
with:
|
||||
path-to-profile: cover.out
|
||||
flag-name: Go-${{ matrix.go_version }}
|
||||
parallel: true
|
||||
|
||||
post-tests:
|
||||
runs-on: ubuntu-latest
|
||||
needs: tests
|
||||
steps:
|
||||
- name: Close coverage report
|
||||
uses: shogo82148/actions-goveralls@v1
|
||||
with:
|
||||
parallel-finished: true
|
||||
|
||||
127
CHANGELOG.md
127
CHANGELOG.md
@@ -5,6 +5,133 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 0.10.1 - 06-10-2025
|
||||
|
||||
### Refactor
|
||||
|
||||
- renamed swagger to openapi in error messages
|
||||
|
||||
## 0.10.0 - 28-02-2024
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
- remove support to golang 1.19
|
||||
|
||||
### Updated
|
||||
|
||||
- change jsonschema lib to use again `invopop/jsonschema` one, since now it support additionalProperties instead of patternProperties
|
||||
|
||||
## 0.9.0 - 17-04-2023
|
||||
|
||||
### Added
|
||||
|
||||
- add new fields to Definition:
|
||||
- Security
|
||||
- Tags
|
||||
- Summary
|
||||
- Description
|
||||
- Deprecated
|
||||
- Extensions
|
||||
|
||||
### Updated
|
||||
|
||||
- update kin-openapi to 0.114.0: this change removes components from exposed oas (if not manually set). In this update of kin-openapi, there is a breaking change with the T struct, which now accepts component as pointer.
|
||||
|
||||
## 0.8.0 - 23-12-2022
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
- add `TransformPathToOasPath(path string) string` method to apirouter.Router interface to handle different types of paths parameters. If you use one of the supported routers, you should do nothing;
|
||||
|
||||
## 0.7.0 - 22-12-2022
|
||||
|
||||
This is a big major release. The main achievement is to increase the usability of this library to all the routers.
|
||||
Below are listed the breaking changes you should care when update the version.
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
- `apirouter.NewGorillaMuxRouter` is now `gorilla.NewRouter` (exposed by package `github.com/davidebianchi/gswagger/support/gorilla`).
|
||||
- removed `apirouter.HandlerFunc`. Now it is exposed by `gorilla.HandlerFunc`
|
||||
- changed `apirouter.Router` interface:
|
||||
- now it accept a generics `HandlerFunc` to define the handler function
|
||||
- add method `SwaggerHandler(contentType string, json []byte) HandlerFunc`
|
||||
- `NewRouter` function now accept `HandlerFunc` as generics
|
||||
- drop support to golang <= 1.17
|
||||
- `GenerateAndExposeSwagger` renamed to `GenerateAndExposeOpenapi`
|
||||
|
||||
### Feature
|
||||
|
||||
- support to different types of routers
|
||||
- add [fiber](https://github.com/gofiber/fiber) support
|
||||
- add [echo](https://echo.labstack.com/) support
|
||||
|
||||
## 0.6.1 - 17-11-2022
|
||||
|
||||
### Changed
|
||||
|
||||
- change jsonschema lib to `mia-platform/jsonschema v0.1.0`. This update removes the `patternProperties` with `additionalProperties` from all schemas
|
||||
- remove use of deprecated io/ioutil lib
|
||||
|
||||
## 0.6.0 - 04-11-2022
|
||||
|
||||
### Added
|
||||
|
||||
- Tags support to `router.AddRoute` accepted definition
|
||||
|
||||
## 0.5.1 - 03-10-2022
|
||||
|
||||
### Fixed
|
||||
|
||||
- upgrade deps
|
||||
|
||||
## v0.5.0 - 05-08-2022
|
||||
|
||||
### Added
|
||||
|
||||
- path params are auto generated if not set
|
||||
|
||||
## v0.4.0 - 02-08-2022
|
||||
|
||||
### Changed
|
||||
|
||||
- change jsonschema lib to `invopop/jsonschema v0.5.0`. This updates remove the `additionalProperties: true` from all the schemas, as it is the default value
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
- modified Router interface by sorting addRoute arguments in a different manner: first method and then path
|
||||
To migrate, all the router implementation must be updated with the Router interface change.
|
||||
|
||||
Before:
|
||||
|
||||
```go
|
||||
type Router interface {
|
||||
AddRoute(path, method string, handler HandlerFunc) Route
|
||||
}
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```go
|
||||
type Router interface {
|
||||
AddRoute(method, path string, handler HandlerFunc) Route
|
||||
}
|
||||
```
|
||||
|
||||
### Updates
|
||||
|
||||
- kin-openapi@v0.98.0
|
||||
- go-openapi/swag@v0.21.1
|
||||
- labstack/echo/v4@v4.7.2
|
||||
|
||||
## v0.3.0 - 10-11-2021
|
||||
|
||||
### Added
|
||||
|
||||
- handle router with path prefix
|
||||
- add SubRouter method to use a new sub router
|
||||
|
||||
## v0.2.0 - 16-10-2021
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
23
Makefile
Normal file
23
Makefile
Normal file
@@ -0,0 +1,23 @@
|
||||
VERSION ?= latest
|
||||
|
||||
# Create a variable that contains the current date in UTC
|
||||
# Different flow if this script is running on Darwin or Linux machines.
|
||||
ifeq (Darwin,$(shell uname))
|
||||
NOW_DATE = $(shell date -u +%d-%m-%Y)
|
||||
else
|
||||
NOW_DATE = $(shell date -u -I)
|
||||
endif
|
||||
|
||||
all: test
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
go test ./... -coverprofile coverage.out
|
||||
|
||||
.PHONY: version
|
||||
version:
|
||||
sed -i.bck "s|## Unreleased|## Unreleased\n\n## ${VERSION} - ${NOW_DATE}|g" "CHANGELOG.md"
|
||||
rm -fr "CHANGELOG.md.bck"
|
||||
git add "CHANGELOG.md"
|
||||
git commit -m "Upgrade version to v${VERSION}"
|
||||
git tag v${VERSION}
|
||||
56
README.md
56
README.md
@@ -1,6 +1,7 @@
|
||||
<div align="center">
|
||||
|
||||
|
||||
[![Build Status][github-actions-svg]][github-actions]
|
||||
[](https://coveralls.io/github/davidebianchi/gswagger?branch=main)
|
||||
[![Go Report Card][go-report-card]][go-report-card-link]
|
||||
[![GoDoc][godoc-svg]][godoc-link]
|
||||
|
||||
@@ -10,11 +11,13 @@
|
||||
|
||||
Generate an openapi spec dynamically based on the types used to handle request and response.
|
||||
|
||||
It works with any router which support handler net/http HandlerFunc compatible.
|
||||
It works with any router, it is simple to add support to your router implementing the [apirouter](apirouter/router.go) interface.
|
||||
|
||||
The routers supported out of the box are:
|
||||
|
||||
- [gorilla-mux](https://github.com/gorilla/mux)
|
||||
- [fiber](https://github.com/gofiber/fiber)
|
||||
- [echo](https://echo.labstack.com/)
|
||||
|
||||
This lib uses [kin-openapi] to automatically generate and serve a swagger file.
|
||||
|
||||
@@ -22,9 +25,15 @@ To convert struct to schemas, we use [jsonschema] library.
|
||||
The struct must contains the appropriate struct tags to be inserted in json schema to generate the schema dynamically.
|
||||
It is always possible to add a totally custom swagger schema using [kin-openapi].
|
||||
|
||||
## Usage
|
||||
## Install
|
||||
|
||||
To add a router not handled out of the box, it must implements the [Router interface](./apirouter/router.go).
|
||||
To use it, install with
|
||||
|
||||
```sh
|
||||
go get github.com/davidebianchi/gswagger
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
An example usage of this lib with gorilla mux:
|
||||
|
||||
@@ -32,7 +41,7 @@ An example usage of this lib with gorilla mux:
|
||||
context := context.Background()
|
||||
muxRouter := mux.NewRouter()
|
||||
|
||||
router, err := swagger.NewRouter(apirouter.NewGorillaMuxRouter(muxRouter), swagger.Options{
|
||||
router, _ := swagger.NewRouter(gorilla.NewRouter(muxRouter), swagger.Options{
|
||||
Context: context,
|
||||
Openapi: &openapi3.T{
|
||||
Info: &openapi3.Info{
|
||||
@@ -53,7 +62,6 @@ type User struct {
|
||||
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"`
|
||||
}
|
||||
@@ -98,9 +106,41 @@ operation := swagger.NewOperation()
|
||||
operation.AddRequestBody(requestBody)
|
||||
|
||||
router.AddRawRoute(http.MethodPost, "/cars", okHandler, operation)
|
||||
|
||||
router.GenerateAndExposeOpenapi()
|
||||
```
|
||||
|
||||
This configuration will output the schema shown [here](testdata/users_employees.json)
|
||||
This configuration will output the schema shown [here](./support/gorilla/testdata/examples-users.json).
|
||||
|
||||
## Auto generated path params schema
|
||||
|
||||
The path params, if not set in schema, are auto generated from the path.
|
||||
The format of the path parameters depends on the router library you are using, as explained below.
|
||||
|
||||
### Gorilla Mux
|
||||
|
||||
Gorilla Mux supports the path parameters as `{someParam}`, for example as in `/users/{userId}`.
|
||||
|
||||
Here is the [example test](./support/gorilla/examples_test.go).
|
||||
|
||||
The generated oas schema will contains `userId`, `carId` and `driverId` as path params set to string.
|
||||
If only one params is set, you must specify manually all the path params.
|
||||
|
||||
The generated OAS for this test case is visible [here](./support/gorilla/testdata/examples-users.json).
|
||||
|
||||
### Fiber
|
||||
Fiber supports the path parameters as `:someParam`, for example as in `/users/:userId`.
|
||||
|
||||
Here is the [example test](./support/fiber/integration_test.go)
|
||||
|
||||
## SubRouter
|
||||
|
||||
It is possible to create a new sub router from the swagger.Router.
|
||||
It is possible to add a prefix to all the routes created under the specific router (instead of use the router specific methods, if given, or repeat the prefix for every route).
|
||||
|
||||
It could also be useful if you need a sub router to create a group of APIs which use the same middleware (for example,this could be achieved by the SubRouter features of gorilla mux, for example).
|
||||
|
||||
To see the SubRouter example, please see the integration test of one of the supported routers.
|
||||
|
||||
### FAQ
|
||||
|
||||
@@ -129,7 +169,7 @@ see the [tags on this repository](https://github.com/davidebianchi/gswagger/tags
|
||||
|
||||
<!-- Reference -->
|
||||
[kin-openapi]: https://github.com/getkin/kin-openapi
|
||||
[jsonschema]: https://github.com/alecthomas/jsonschema
|
||||
[jsonschema]: https://github.com/mia-platform/jsonschema
|
||||
[github-actions]: https://github.com/davidebianchi/gswagger/actions
|
||||
[github-actions-svg]: https://github.com/davidebianchi/gswagger/workflows/Test%20and%20build/badge.svg
|
||||
[godoc-svg]: https://godoc.org/github.com/davidebianchi/gswagger?status.svg
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
package apirouter
|
||||
|
||||
import "github.com/gorilla/mux"
|
||||
|
||||
type gorillaRouter struct {
|
||||
router *mux.Router
|
||||
}
|
||||
|
||||
func (r gorillaRouter) AddRoute(path string, method string, handler HandlerFunc) Route {
|
||||
return r.router.HandleFunc(path, handler).Methods(method)
|
||||
}
|
||||
|
||||
func NewGorillaMuxRouter(router *mux.Router) Router {
|
||||
return gorillaRouter{
|
||||
router: router,
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package apirouter
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGorillaMuxRouter(t *testing.T) {
|
||||
muxRouter := mux.NewRouter()
|
||||
ar := NewGorillaMuxRouter(muxRouter)
|
||||
|
||||
t.Run("create a new api router", func(t *testing.T) {
|
||||
require.Implements(t, (*Router)(nil), ar)
|
||||
})
|
||||
|
||||
t.Run("add new route", func(t *testing.T) {
|
||||
route := ar.AddRoute("/foo", http.MethodGet, func(w http.ResponseWriter, req *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
w.Write(nil)
|
||||
})
|
||||
|
||||
_, ok := route.(*mux.Route)
|
||||
require.True(t, ok)
|
||||
|
||||
t.Run("router exposes correctly api", func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest(http.MethodGet, "/foo", nil)
|
||||
|
||||
muxRouter.ServeHTTP(w, r)
|
||||
|
||||
require.Equal(t, http.StatusOK, w.Result().StatusCode)
|
||||
})
|
||||
|
||||
t.Run("router exposes api only to the specific method", func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest(http.MethodPost, "/foo", nil)
|
||||
|
||||
muxRouter.ServeHTTP(w, r)
|
||||
|
||||
require.Equal(t, http.StatusMethodNotAllowed, w.Result().StatusCode)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -1,12 +1,7 @@
|
||||
package apirouter
|
||||
|
||||
import "net/http"
|
||||
|
||||
// Handler is the http type handler
|
||||
type HandlerFunc func(w http.ResponseWriter, req *http.Request)
|
||||
|
||||
type Router interface {
|
||||
AddRoute(path string, method string, handler HandlerFunc) Route
|
||||
type Router[HandlerFunc any, Route any] interface {
|
||||
AddRoute(method string, path string, handler HandlerFunc) Route
|
||||
SwaggerHandler(contentType string, blob []byte) HandlerFunc
|
||||
TransformPathToOasPath(path string) string
|
||||
}
|
||||
|
||||
type Route interface{}
|
||||
|
||||
15
apirouter/transformpath.go
Normal file
15
apirouter/transformpath.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package apirouter
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func TransformPathParamsWithColon(path string) string {
|
||||
pathParams := strings.Split(path, "/")
|
||||
for i, param := range pathParams {
|
||||
if strings.HasPrefix(param, ":") {
|
||||
pathParams[i] = strings.Replace(param, ":", "{", 1) + "}"
|
||||
}
|
||||
}
|
||||
return strings.Join(pathParams, "/")
|
||||
}
|
||||
59
apirouter/transformpath_test.go
Normal file
59
apirouter/transformpath_test.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package apirouter
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestTransformPathParamsWithColon(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
path string
|
||||
expectedPath string
|
||||
}{
|
||||
{
|
||||
name: "only /",
|
||||
path: "/",
|
||||
expectedPath: "/",
|
||||
},
|
||||
{
|
||||
name: "without params",
|
||||
path: "/foo",
|
||||
expectedPath: "/foo",
|
||||
},
|
||||
{
|
||||
name: "without params ending with /",
|
||||
path: "/foo/",
|
||||
expectedPath: "/foo/",
|
||||
},
|
||||
{
|
||||
name: "with params",
|
||||
path: "/foo/:par1",
|
||||
expectedPath: "/foo/{par1}",
|
||||
},
|
||||
{
|
||||
name: "with params ending with /",
|
||||
path: "/foo/:par1/",
|
||||
expectedPath: "/foo/{par1}/",
|
||||
},
|
||||
{
|
||||
name: "with multiple params",
|
||||
path: "/:par1/:par2/:par3",
|
||||
expectedPath: "/{par1}/{par2}/{par3}",
|
||||
},
|
||||
{
|
||||
name: "with multiple params ending with /",
|
||||
path: "/:par1/:par2/:par3/",
|
||||
expectedPath: "/{par1}/{par2}/{par3}/",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
actual := TransformPathParamsWithColon(test.path)
|
||||
|
||||
require.Equal(t, test.expectedPath, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
49
go.mod
49
go.mod
@@ -1,14 +1,47 @@
|
||||
module github.com/davidebianchi/gswagger
|
||||
|
||||
go 1.15
|
||||
go 1.24
|
||||
|
||||
require (
|
||||
github.com/alecthomas/jsonschema v0.0.0-20210920000243-787cd8204a0d
|
||||
github.com/getkin/kin-openapi v0.79.0
|
||||
github.com/getkin/kin-openapi v0.131.0
|
||||
github.com/ghodss/yaml v1.0.0
|
||||
github.com/go-openapi/swag v0.19.15 // indirect
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/iancoleman/orderedmap v0.2.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/gofiber/fiber/v2 v2.52.5
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/invopop/jsonschema v0.12.0
|
||||
github.com/labstack/echo/v4 v4.12.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/buger/jsonparser v1.1.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/google/uuid v1.5.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/klauspost/compress v1.17.0 // indirect
|
||||
github.com/labstack/gommon v0.4.2 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
||||
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect
|
||||
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect
|
||||
github.com/perimeterx/marshmallow v1.1.5 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.51.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
|
||||
golang.org/x/crypto v0.22.0 // indirect
|
||||
golang.org/x/net v0.24.0 // indirect
|
||||
golang.org/x/sys v0.19.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
116
go.sum
116
go.sum
@@ -1,54 +1,92 @@
|
||||
github.com/alecthomas/jsonschema v0.0.0-20210920000243-787cd8204a0d h1:sUHuJQ3zwLmUgKM1v51WLWRtoy9r+hc/m7DoNftpUdA=
|
||||
github.com/alecthomas/jsonschema v0.0.0-20210920000243-787cd8204a0d/go.mod h1:/n6+1/DWPltRLWL/VKyUxg6tzsl5kHUCcraimt4vr60=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
||||
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
|
||||
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
||||
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.79.0 h1:YLZIgIhZLq9z5WFHHIK+oWORRfn6jjwr7qN0xak0xbE=
|
||||
github.com/getkin/kin-openapi v0.79.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg=
|
||||
github.com/getkin/kin-openapi v0.131.0 h1:NO2UeHnFKRYhZ8wg6Nyh5Cq7dHk4suQQr72a4pMrDxE=
|
||||
github.com/getkin/kin-openapi v0.131.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58=
|
||||
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.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
|
||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
|
||||
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA=
|
||||
github.com/iancoleman/orderedmap v0.2.0 h1:sq1N/TFpYH++aViPcaKjys3bDClUEU7s5B+z6jq8pNA=
|
||||
github.com/iancoleman/orderedmap v0.2.0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA=
|
||||
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
|
||||
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo=
|
||||
github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
|
||||
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
||||
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI=
|
||||
github.com/invopop/jsonschema v0.12.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
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/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM=
|
||||
github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0=
|
||||
github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM=
|
||||
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
|
||||
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY=
|
||||
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw=
|
||||
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c=
|
||||
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o=
|
||||
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
|
||||
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
|
||||
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/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/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
|
||||
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
|
||||
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
|
||||
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
||||
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
|
||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
|
||||
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
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/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
package swagger_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
swagger "github.com/davidebianchi/gswagger"
|
||||
"github.com/davidebianchi/gswagger/apirouter"
|
||||
"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, swagger.DefaultJSONDocumentationPath, 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 := swagger.NewRouter(apirouter.NewGorillaMuxRouter(muxRouter), swagger.Options{
|
||||
Context: context,
|
||||
Openapi: &openapi3.T{
|
||||
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 := swagger.Operation{}
|
||||
|
||||
_, err = router.AddRawRoute(http.MethodGet, "/hello", handler, operation)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = router.GenerateAndExposeSwagger()
|
||||
require.NoError(t, err)
|
||||
|
||||
return muxRouter
|
||||
}
|
||||
120
main.go
120
main.go
@@ -13,29 +13,35 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrGenerateSwagger throws when fails the marshalling of the swagger struct.
|
||||
ErrGenerateSwagger = errors.New("fail to generate swagger")
|
||||
// ErrValidatingSwagger throws when given swagger params are not correct.
|
||||
ErrValidatingSwagger = errors.New("fails to validate swagger")
|
||||
// ErrGenerateOAS throws when fails the marshalling of the swagger struct.
|
||||
ErrGenerateOAS = errors.New("fail to generate openapi")
|
||||
// ErrValidatingOAS throws when given openapi params are not correct.
|
||||
ErrValidatingOAS = errors.New("fails to validate openapi")
|
||||
|
||||
// Deprecated: ErrGenerateSwagger has been deprecated, use ErrGenerateOAS instead.
|
||||
ErrGenerateSwagger = ErrGenerateOAS
|
||||
// Deprecated: ErrValidatingSwagger has been deprecated, use ErrValidatingOAS instead.
|
||||
ErrValidatingSwagger = ErrValidatingOAS
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultJSONDocumentationPath is the path of the swagger documentation in json format.
|
||||
// DefaultJSONDocumentationPath is the path of the openapi documentation in json format.
|
||||
DefaultJSONDocumentationPath = "/documentation/json"
|
||||
// DefaultYAMLDocumentationPath is the path of the swagger documentation in yaml format.
|
||||
// DefaultYAMLDocumentationPath is the path of the openapi documentation in yaml format.
|
||||
DefaultYAMLDocumentationPath = "/documentation/yaml"
|
||||
defaultOpenapiVersion = "3.0.0"
|
||||
)
|
||||
|
||||
// Router handle the api router and the swagger schema.
|
||||
// Router handle the api router and the openapi schema.
|
||||
// api router supported out of the box are:
|
||||
// - gorilla mux
|
||||
type Router struct {
|
||||
router apirouter.Router
|
||||
type Router[HandlerFunc, Route any] struct {
|
||||
router apirouter.Router[HandlerFunc, Route]
|
||||
swaggerSchema *openapi3.T
|
||||
context context.Context
|
||||
jsonDocumentationPath string
|
||||
yamlDocumentationPath string
|
||||
pathPrefix string
|
||||
}
|
||||
|
||||
// Options to be passed to create the new router and swagger
|
||||
@@ -46,13 +52,15 @@ type Options struct {
|
||||
JSONDocumentationPath string
|
||||
// YAMLDocumentationPath is the path exposed by yaml endpoint. Default to /documentation/yaml.
|
||||
YAMLDocumentationPath string
|
||||
// Add path prefix to add to every router path.
|
||||
PathPrefix string
|
||||
}
|
||||
|
||||
// NewRouter generate new router with swagger. Default to OpenAPI 3.0.0
|
||||
func NewRouter(router apirouter.Router, options Options) (*Router, error) {
|
||||
swagger, err := generateNewValidSwagger(options.Openapi)
|
||||
// NewRouter generate new router with openapi. Default to OpenAPI 3.0.0
|
||||
func NewRouter[HandlerFunc, Route any](router apirouter.Router[HandlerFunc, Route], options Options) (*Router[HandlerFunc, Route], error) {
|
||||
openapi, err := generateNewValidOpenapi(options.Openapi)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %s", ErrValidatingSwagger, err)
|
||||
return nil, fmt.Errorf("%w: %s", ErrValidatingOAS, err)
|
||||
}
|
||||
|
||||
var ctx = options.Context
|
||||
@@ -76,65 +84,73 @@ func NewRouter(router apirouter.Router, options Options) (*Router, error) {
|
||||
jsonDocumentationPath = options.JSONDocumentationPath
|
||||
}
|
||||
|
||||
return &Router{
|
||||
return &Router[HandlerFunc, Route]{
|
||||
router: router,
|
||||
swaggerSchema: swagger,
|
||||
swaggerSchema: openapi,
|
||||
context: ctx,
|
||||
yamlDocumentationPath: yamlDocumentationPath,
|
||||
jsonDocumentationPath: jsonDocumentationPath,
|
||||
pathPrefix: options.PathPrefix,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func generateNewValidSwagger(swagger *openapi3.T) (*openapi3.T, error) {
|
||||
if swagger == nil {
|
||||
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 is required")
|
||||
}
|
||||
if swagger.Info.Title == "" {
|
||||
return nil, fmt.Errorf("swagger info title is required")
|
||||
}
|
||||
if swagger.Info.Version == "" {
|
||||
return nil, fmt.Errorf("swagger info version is required")
|
||||
}
|
||||
|
||||
return swagger, nil
|
||||
type SubRouterOptions struct {
|
||||
PathPrefix string
|
||||
}
|
||||
|
||||
// GenerateAndExposeSwagger creates a /documentation/json route on router and
|
||||
func (r Router[HandlerFunc, Route]) SubRouter(router apirouter.Router[HandlerFunc, Route], opts SubRouterOptions) (*Router[HandlerFunc, Route], error) {
|
||||
return &Router[HandlerFunc, Route]{
|
||||
router: router,
|
||||
swaggerSchema: r.swaggerSchema,
|
||||
context: r.context,
|
||||
jsonDocumentationPath: r.jsonDocumentationPath,
|
||||
yamlDocumentationPath: r.yamlDocumentationPath,
|
||||
pathPrefix: opts.PathPrefix,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func generateNewValidOpenapi(openapi *openapi3.T) (*openapi3.T, error) {
|
||||
if openapi == nil {
|
||||
return nil, fmt.Errorf("openapi is required")
|
||||
}
|
||||
if openapi.OpenAPI == "" {
|
||||
openapi.OpenAPI = defaultOpenapiVersion
|
||||
}
|
||||
if openapi.Paths == nil {
|
||||
openapi.Paths = &openapi3.Paths{}
|
||||
}
|
||||
|
||||
if openapi.Info == nil {
|
||||
return nil, fmt.Errorf("openapi info is required")
|
||||
}
|
||||
if openapi.Info.Title == "" {
|
||||
return nil, fmt.Errorf("openapi info title is required")
|
||||
}
|
||||
if openapi.Info.Version == "" {
|
||||
return nil, fmt.Errorf("openapi info version is required")
|
||||
}
|
||||
|
||||
return openapi, nil
|
||||
}
|
||||
|
||||
// GenerateAndExposeOpenapi creates a /documentation/json route on router and
|
||||
// expose the generated swagger
|
||||
func (r Router) GenerateAndExposeSwagger() error {
|
||||
func (r Router[_, _]) GenerateAndExposeOpenapi() error {
|
||||
if err := r.swaggerSchema.Validate(r.context); err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrValidatingSwagger, err)
|
||||
return fmt.Errorf("%w: %s", ErrValidatingOAS, err)
|
||||
}
|
||||
|
||||
jsonSwagger, err := r.swaggerSchema.MarshalJSON()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w json marshal: %s", ErrGenerateSwagger, err)
|
||||
return fmt.Errorf("%w json marshal: %s", ErrGenerateOAS, err)
|
||||
}
|
||||
r.router.AddRoute(r.jsonDocumentationPath, http.MethodGet, func(w http.ResponseWriter, req *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(jsonSwagger)
|
||||
})
|
||||
r.router.AddRoute(http.MethodGet, r.jsonDocumentationPath, r.router.SwaggerHandler("application/json", jsonSwagger))
|
||||
|
||||
yamlSwagger, err := yaml.JSONToYAML(jsonSwagger)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w yaml marshal: %s", ErrGenerateSwagger, err)
|
||||
return fmt.Errorf("%w yaml marshal: %s", ErrGenerateOAS, err)
|
||||
}
|
||||
r.router.AddRoute(r.yamlDocumentationPath, http.MethodGet, func(w http.ResponseWriter, req *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(yamlSwagger)
|
||||
})
|
||||
r.router.AddRoute(http.MethodGet, r.yamlDocumentationPath, r.router.SwaggerHandler("text/plain", yamlSwagger))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
262
main_test.go
262
main_test.go
@@ -4,20 +4,21 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/davidebianchi/gswagger/apirouter"
|
||||
"github.com/davidebianchi/gswagger/support/gorilla"
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewRouter(t *testing.T) {
|
||||
mRouter := apirouter.NewGorillaMuxRouter(mux.NewRouter())
|
||||
muxRouter := mux.NewRouter()
|
||||
mAPIRouter := gorilla.NewRouter(muxRouter)
|
||||
|
||||
info := &openapi3.Info{
|
||||
Title: "my title",
|
||||
@@ -25,25 +26,25 @@ func TestNewRouter(t *testing.T) {
|
||||
}
|
||||
openapi := &openapi3.T{
|
||||
Info: info,
|
||||
Paths: openapi3.Paths{},
|
||||
Paths: &openapi3.Paths{},
|
||||
}
|
||||
|
||||
t.Run("not ok - invalid Openapi option", func(t *testing.T) {
|
||||
r, err := NewRouter(mRouter, Options{})
|
||||
r, err := NewRouter(mAPIRouter, Options{})
|
||||
|
||||
require.Nil(t, r)
|
||||
require.EqualError(t, err, fmt.Sprintf("%s: swagger is required", ErrValidatingSwagger))
|
||||
require.EqualError(t, err, fmt.Sprintf("%s: openapi is required", ErrValidatingOAS))
|
||||
})
|
||||
|
||||
t.Run("ok - with default context", func(t *testing.T) {
|
||||
r, err := NewRouter(mRouter, Options{
|
||||
r, err := NewRouter(mAPIRouter, Options{
|
||||
Openapi: openapi,
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, &Router{
|
||||
require.Equal(t, &Router[gorilla.HandlerFunc, gorilla.Route]{
|
||||
context: context.Background(),
|
||||
router: mRouter,
|
||||
router: mAPIRouter,
|
||||
swaggerSchema: openapi,
|
||||
jsonDocumentationPath: DefaultJSONDocumentationPath,
|
||||
yamlDocumentationPath: DefaultYAMLDocumentationPath,
|
||||
@@ -53,15 +54,15 @@ func TestNewRouter(t *testing.T) {
|
||||
t.Run("ok - with custom context", func(t *testing.T) {
|
||||
type key struct{}
|
||||
ctx := context.WithValue(context.Background(), key{}, "value")
|
||||
r, err := NewRouter(mRouter, Options{
|
||||
r, err := NewRouter(mAPIRouter, Options{
|
||||
Openapi: openapi,
|
||||
Context: ctx,
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, &Router{
|
||||
require.Equal(t, &Router[gorilla.HandlerFunc, gorilla.Route]{
|
||||
context: ctx,
|
||||
router: mRouter,
|
||||
router: mAPIRouter,
|
||||
swaggerSchema: openapi,
|
||||
jsonDocumentationPath: DefaultJSONDocumentationPath,
|
||||
yamlDocumentationPath: DefaultYAMLDocumentationPath,
|
||||
@@ -71,7 +72,7 @@ func TestNewRouter(t *testing.T) {
|
||||
t.Run("ok - with custom docs paths", func(t *testing.T) {
|
||||
type key struct{}
|
||||
ctx := context.WithValue(context.Background(), key{}, "value")
|
||||
r, err := NewRouter(mRouter, Options{
|
||||
r, err := NewRouter(mAPIRouter, Options{
|
||||
Openapi: openapi,
|
||||
Context: ctx,
|
||||
JSONDocumentationPath: "/json/path",
|
||||
@@ -79,9 +80,9 @@ func TestNewRouter(t *testing.T) {
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, &Router{
|
||||
require.Equal(t, &Router[gorilla.HandlerFunc, gorilla.Route]{
|
||||
context: ctx,
|
||||
router: mRouter,
|
||||
router: mAPIRouter,
|
||||
swaggerSchema: openapi,
|
||||
jsonDocumentationPath: "/json/path",
|
||||
yamlDocumentationPath: "/yaml/path",
|
||||
@@ -91,7 +92,7 @@ func TestNewRouter(t *testing.T) {
|
||||
t.Run("ko - json documentation path does not start with /", func(t *testing.T) {
|
||||
type key struct{}
|
||||
ctx := context.WithValue(context.Background(), key{}, "value")
|
||||
r, err := NewRouter(mRouter, Options{
|
||||
r, err := NewRouter(mAPIRouter, Options{
|
||||
Openapi: openapi,
|
||||
Context: ctx,
|
||||
JSONDocumentationPath: "json/path",
|
||||
@@ -105,7 +106,7 @@ func TestNewRouter(t *testing.T) {
|
||||
t.Run("ko - yaml documentation path does not start with /", func(t *testing.T) {
|
||||
type key struct{}
|
||||
ctx := context.WithValue(context.Background(), key{}, "value")
|
||||
r, err := NewRouter(mRouter, Options{
|
||||
r, err := NewRouter(mAPIRouter, Options{
|
||||
Openapi: openapi,
|
||||
Context: ctx,
|
||||
JSONDocumentationPath: "/json/path",
|
||||
@@ -118,50 +119,50 @@ func TestNewRouter(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGenerateValidSwagger(t *testing.T) {
|
||||
t.Run("not ok - empty swagger info", func(t *testing.T) {
|
||||
swagger := &openapi3.T{}
|
||||
t.Run("not ok - empty openapi info", func(t *testing.T) {
|
||||
openapi := &openapi3.T{}
|
||||
|
||||
swagger, err := generateNewValidSwagger(swagger)
|
||||
require.Nil(t, swagger)
|
||||
require.EqualError(t, err, "swagger info is required")
|
||||
openapi, err := generateNewValidOpenapi(openapi)
|
||||
require.Nil(t, openapi)
|
||||
require.EqualError(t, err, "openapi info is required")
|
||||
})
|
||||
|
||||
t.Run("not ok - empty info title", func(t *testing.T) {
|
||||
swagger := &openapi3.T{
|
||||
openapi := &openapi3.T{
|
||||
Info: &openapi3.Info{},
|
||||
}
|
||||
|
||||
swagger, err := generateNewValidSwagger(swagger)
|
||||
require.Nil(t, swagger)
|
||||
require.EqualError(t, err, "swagger info title is required")
|
||||
openapi, err := generateNewValidOpenapi(openapi)
|
||||
require.Nil(t, openapi)
|
||||
require.EqualError(t, err, "openapi info title is required")
|
||||
})
|
||||
|
||||
t.Run("not ok - empty info version", func(t *testing.T) {
|
||||
swagger := &openapi3.T{
|
||||
openapi := &openapi3.T{
|
||||
Info: &openapi3.Info{
|
||||
Title: "title",
|
||||
},
|
||||
}
|
||||
|
||||
swagger, err := generateNewValidSwagger(swagger)
|
||||
require.Nil(t, swagger)
|
||||
require.EqualError(t, err, "swagger info version is required")
|
||||
openapi, err := generateNewValidOpenapi(openapi)
|
||||
require.Nil(t, openapi)
|
||||
require.EqualError(t, err, "openapi info version is required")
|
||||
})
|
||||
|
||||
t.Run("ok - custom swagger", func(t *testing.T) {
|
||||
swagger := &openapi3.T{
|
||||
t.Run("ok - custom openapi", func(t *testing.T) {
|
||||
openapi := &openapi3.T{
|
||||
Info: &openapi3.Info{},
|
||||
}
|
||||
|
||||
swagger, err := generateNewValidSwagger(swagger)
|
||||
require.Nil(t, swagger)
|
||||
require.EqualError(t, err, "swagger info title is required")
|
||||
openapi, err := generateNewValidOpenapi(openapi)
|
||||
require.Nil(t, openapi)
|
||||
require.EqualError(t, err, "openapi 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("not ok - openapi is required", func(t *testing.T) {
|
||||
openapi, err := generateNewValidOpenapi(nil)
|
||||
require.Nil(t, openapi)
|
||||
require.EqualError(t, err, "openapi is required")
|
||||
})
|
||||
|
||||
t.Run("ok", func(t *testing.T) {
|
||||
@@ -169,30 +170,30 @@ func TestGenerateValidSwagger(t *testing.T) {
|
||||
Title: "my title",
|
||||
Version: "my version",
|
||||
}
|
||||
swagger := &openapi3.T{
|
||||
openapi := &openapi3.T{
|
||||
Info: info,
|
||||
}
|
||||
|
||||
swagger, err := generateNewValidSwagger(swagger)
|
||||
openapi, err := generateNewValidOpenapi(openapi)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, &openapi3.T{
|
||||
OpenAPI: defaultOpenapiVersion,
|
||||
Info: info,
|
||||
Paths: openapi3.Paths{},
|
||||
}, swagger)
|
||||
Paths: &openapi3.Paths{},
|
||||
}, openapi)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGenerateAndExposeSwagger(t *testing.T) {
|
||||
t.Run("fails swagger validation", func(t *testing.T) {
|
||||
t.Run("fails openapi validation", func(t *testing.T) {
|
||||
mRouter := mux.NewRouter()
|
||||
router, err := NewRouter(apirouter.NewGorillaMuxRouter(mRouter), Options{
|
||||
router, err := NewRouter(gorilla.NewRouter(mRouter), Options{
|
||||
Openapi: &openapi3.T{
|
||||
Info: &openapi3.Info{
|
||||
Title: "title",
|
||||
Version: "version",
|
||||
},
|
||||
Components: openapi3.Components{
|
||||
Components: &openapi3.Components{
|
||||
Schemas: map[string]*openapi3.SchemaRef{
|
||||
"&%": {},
|
||||
},
|
||||
@@ -201,23 +202,23 @@ func TestGenerateAndExposeSwagger(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = router.GenerateAndExposeSwagger()
|
||||
err = router.GenerateAndExposeOpenapi()
|
||||
require.Error(t, err)
|
||||
require.True(t, strings.HasPrefix(err.Error(), fmt.Sprintf("%s:", ErrValidatingSwagger)))
|
||||
require.True(t, strings.HasPrefix(err.Error(), fmt.Sprintf("%s:", ErrValidatingOAS)))
|
||||
})
|
||||
|
||||
t.Run("correctly expose json documentation from loaded swagger file", func(t *testing.T) {
|
||||
t.Run("correctly expose json documentation from loaded openapi file", func(t *testing.T) {
|
||||
mRouter := mux.NewRouter()
|
||||
|
||||
swagger, err := openapi3.NewLoader().LoadFromFile("testdata/users_employees.json")
|
||||
openapi, err := openapi3.NewLoader().LoadFromFile("testdata/users_employees.json")
|
||||
require.NoError(t, err)
|
||||
|
||||
router, err := NewRouter(apirouter.NewGorillaMuxRouter(mRouter), Options{
|
||||
Openapi: swagger,
|
||||
router, err := NewRouter(gorilla.NewRouter(mRouter), Options{
|
||||
Openapi: openapi,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = router.GenerateAndExposeSwagger()
|
||||
err = router.GenerateAndExposeOpenapi()
|
||||
require.NoError(t, err)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
@@ -228,24 +229,24 @@ func TestGenerateAndExposeSwagger(t *testing.T) {
|
||||
require.True(t, strings.Contains(w.Result().Header.Get("content-type"), "application/json"))
|
||||
|
||||
body := readBody(t, w.Result().Body)
|
||||
actual, err := ioutil.ReadFile("testdata/users_employees.json")
|
||||
actual, err := os.ReadFile("testdata/users_employees.json")
|
||||
require.NoError(t, err)
|
||||
require.JSONEq(t, string(actual), body)
|
||||
})
|
||||
|
||||
t.Run("correctly expose json documentation from loaded swagger file - custom path", func(t *testing.T) {
|
||||
t.Run("correctly expose json documentation from loaded openapi file - custom path", func(t *testing.T) {
|
||||
mRouter := mux.NewRouter()
|
||||
|
||||
swagger, err := openapi3.NewLoader().LoadFromFile("testdata/users_employees.json")
|
||||
openapi, err := openapi3.NewLoader().LoadFromFile("testdata/users_employees.json")
|
||||
require.NoError(t, err)
|
||||
|
||||
router, err := NewRouter(apirouter.NewGorillaMuxRouter(mRouter), Options{
|
||||
Openapi: swagger,
|
||||
router, err := NewRouter(gorilla.NewRouter(mRouter), Options{
|
||||
Openapi: openapi,
|
||||
JSONDocumentationPath: "/custom/path",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = router.GenerateAndExposeSwagger()
|
||||
err = router.GenerateAndExposeOpenapi()
|
||||
require.NoError(t, err)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
@@ -256,23 +257,23 @@ func TestGenerateAndExposeSwagger(t *testing.T) {
|
||||
require.True(t, strings.Contains(w.Result().Header.Get("content-type"), "application/json"))
|
||||
|
||||
body := readBody(t, w.Result().Body)
|
||||
actual, err := ioutil.ReadFile("testdata/users_employees.json")
|
||||
actual, err := os.ReadFile("testdata/users_employees.json")
|
||||
require.NoError(t, err)
|
||||
require.JSONEq(t, string(actual), body)
|
||||
})
|
||||
|
||||
t.Run("correctly expose yaml documentation from loaded swagger file", func(t *testing.T) {
|
||||
t.Run("correctly expose yaml documentation from loaded openapi file", func(t *testing.T) {
|
||||
mRouter := mux.NewRouter()
|
||||
|
||||
swagger, err := openapi3.NewLoader().LoadFromFile("testdata/users_employees.json")
|
||||
openapi, err := openapi3.NewLoader().LoadFromFile("testdata/users_employees.json")
|
||||
require.NoError(t, err)
|
||||
|
||||
router, err := NewRouter(apirouter.NewGorillaMuxRouter(mRouter), Options{
|
||||
Openapi: swagger,
|
||||
router, err := NewRouter(gorilla.NewRouter(mRouter), Options{
|
||||
Openapi: openapi,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = router.GenerateAndExposeSwagger()
|
||||
err = router.GenerateAndExposeOpenapi()
|
||||
require.NoError(t, err)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
@@ -283,24 +284,24 @@ func TestGenerateAndExposeSwagger(t *testing.T) {
|
||||
require.True(t, strings.Contains(w.Result().Header.Get("content-type"), "text/plain"))
|
||||
|
||||
body := readBody(t, w.Result().Body)
|
||||
expected, err := ioutil.ReadFile("testdata/users_employees.yaml")
|
||||
expected, err := os.ReadFile("testdata/users_employees.yaml")
|
||||
require.NoError(t, err)
|
||||
require.YAMLEq(t, string(expected), body, string(body))
|
||||
})
|
||||
|
||||
t.Run("correctly expose yaml documentation from loaded swagger file - custom path", func(t *testing.T) {
|
||||
t.Run("correctly expose yaml documentation from loaded openapi file - custom path", func(t *testing.T) {
|
||||
mRouter := mux.NewRouter()
|
||||
|
||||
swagger, err := openapi3.NewLoader().LoadFromFile("testdata/users_employees.json")
|
||||
openapi, err := openapi3.NewLoader().LoadFromFile("testdata/users_employees.json")
|
||||
require.NoError(t, err)
|
||||
|
||||
router, err := NewRouter(apirouter.NewGorillaMuxRouter(mRouter), Options{
|
||||
Openapi: swagger,
|
||||
router, err := NewRouter(gorilla.NewRouter(mRouter), Options{
|
||||
Openapi: openapi,
|
||||
YAMLDocumentationPath: "/custom/path",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = router.GenerateAndExposeSwagger()
|
||||
err = router.GenerateAndExposeOpenapi()
|
||||
require.NoError(t, err)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
@@ -311,16 +312,131 @@ func TestGenerateAndExposeSwagger(t *testing.T) {
|
||||
require.True(t, strings.Contains(w.Result().Header.Get("content-type"), "text/plain"))
|
||||
|
||||
body := readBody(t, w.Result().Body)
|
||||
expected, err := ioutil.ReadFile("testdata/users_employees.yaml")
|
||||
expected, err := os.ReadFile("testdata/users_employees.yaml")
|
||||
require.NoError(t, err)
|
||||
require.YAMLEq(t, string(expected), body, string(body))
|
||||
})
|
||||
|
||||
t.Run("ok - subrouter", func(t *testing.T) {
|
||||
mRouter := mux.NewRouter()
|
||||
|
||||
router, err := NewRouter(gorilla.NewRouter(mRouter), Options{
|
||||
Openapi: &openapi3.T{
|
||||
Info: &openapi3.Info{
|
||||
Title: "test openapi title",
|
||||
Version: "test openapi version",
|
||||
},
|
||||
},
|
||||
JSONDocumentationPath: "/custom/path",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
router.AddRoute(http.MethodGet, "/foo", func(w http.ResponseWriter, req *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("ok"))
|
||||
}, Definitions{})
|
||||
|
||||
mSubRouter := mRouter.NewRoute().Subrouter()
|
||||
subrouter, err := router.SubRouter(gorilla.NewRouter(mSubRouter), SubRouterOptions{
|
||||
PathPrefix: "/prefix",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = subrouter.AddRoute(http.MethodGet, "/taz", func(w http.ResponseWriter, req *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("ok"))
|
||||
}, Definitions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("add route with path equal to prefix path", func(t *testing.T) {
|
||||
_, err = subrouter.AddRoute(http.MethodGet, "", func(w http.ResponseWriter, req *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("ok"))
|
||||
}, Definitions{})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
err = router.GenerateAndExposeOpenapi()
|
||||
require.NoError(t, err)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/custom/path", 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)
|
||||
actual, err := os.ReadFile("testdata/subrouter.json")
|
||||
require.NoError(t, err)
|
||||
require.JSONEq(t, string(actual), body)
|
||||
|
||||
t.Run("test request /prefix", func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/prefix", nil)
|
||||
mRouter.ServeHTTP(w, req)
|
||||
|
||||
require.Equal(t, http.StatusOK, w.Result().StatusCode)
|
||||
})
|
||||
|
||||
t.Run("test request /prefix/taz", func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/prefix/taz", nil)
|
||||
mRouter.ServeHTTP(w, req)
|
||||
|
||||
require.Equal(t, http.StatusOK, w.Result().StatusCode)
|
||||
})
|
||||
|
||||
t.Run("test request /foo", func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/foo", nil)
|
||||
mRouter.ServeHTTP(w, req)
|
||||
|
||||
require.Equal(t, http.StatusOK, w.Result().StatusCode)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("ok - new router with path prefix", func(t *testing.T) {
|
||||
mRouter := mux.NewRouter()
|
||||
|
||||
router, err := NewRouter(gorilla.NewRouter(mRouter), Options{
|
||||
Openapi: &openapi3.T{
|
||||
Info: &openapi3.Info{
|
||||
Title: "test openapi title",
|
||||
Version: "test openapi version",
|
||||
},
|
||||
},
|
||||
JSONDocumentationPath: "/custom/path",
|
||||
PathPrefix: "/prefix",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
router.AddRoute(http.MethodGet, "/foo", func(w http.ResponseWriter, req *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("ok"))
|
||||
}, Definitions{})
|
||||
|
||||
err = router.GenerateAndExposeOpenapi()
|
||||
require.NoError(t, err)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/custom/path", 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)
|
||||
actual, err := os.ReadFile("testdata/router_with_prefix.json")
|
||||
require.NoError(t, err)
|
||||
require.JSONEq(t, string(actual), body, body)
|
||||
})
|
||||
}
|
||||
|
||||
func readBody(t *testing.T, requestBody io.ReadCloser) string {
|
||||
t.Helper()
|
||||
|
||||
body, err := ioutil.ReadAll(requestBody)
|
||||
body, err := io.ReadAll(requestBody)
|
||||
require.NoError(t, err)
|
||||
|
||||
return string(body)
|
||||
|
||||
11
operation.go
11
operation.go
@@ -28,7 +28,7 @@ func (o *Operation) AddRequestBody(requestBody *openapi3.RequestBody) {
|
||||
// but it is always possible to add it manually.
|
||||
func (o *Operation) AddResponse(status int, response *openapi3.Response) {
|
||||
if o.Responses == nil {
|
||||
o.Responses = make(openapi3.Responses)
|
||||
o.Responses = &openapi3.Responses{}
|
||||
}
|
||||
if response.Description == nil {
|
||||
// a description is required by kin openapi, so we set an empty description
|
||||
@@ -37,3 +37,12 @@ func (o *Operation) AddResponse(status int, response *openapi3.Response) {
|
||||
}
|
||||
o.Operation.AddResponse(status, response)
|
||||
}
|
||||
|
||||
func (o *Operation) addSecurityRequirements(securityRequirements SecurityRequirements) {
|
||||
if securityRequirements != nil && o.Security == nil {
|
||||
o.Security = openapi3.NewSecurityRequirements()
|
||||
}
|
||||
for _, securityRequirement := range securityRequirements {
|
||||
o.Security.With(openapi3.SecurityRequirement(securityRequirement))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ func TestNewOperation(t *testing.T) {
|
||||
operation.Responses = openapi3.NewResponses()
|
||||
return operation
|
||||
},
|
||||
expectedJSON: `{"components":{},"info":{"title":"test swagger title","version":"test swagger version"},"openapi":"3.0.0","paths":{"/":{"post":{"requestBody":{"content":{"application/json":{"schema":{"properties":{"bar":{"maximum":15,"minimum":5,"type":"integer"},"foo":{"type":"string"}},"type":"object"}}}},"responses":{"default":{"description":""}}}}}}`,
|
||||
expectedJSON: `{"info":{"title":"test openapi title","version":"test openapi version"},"openapi":"3.0.0","paths":{"/":{"post":{"requestBody":{"content":{"application/json":{"schema":{"properties":{"bar":{"maximum":15,"minimum":5,"type":"integer"},"foo":{"type":"string"}},"type":"object"}}}},"responses":{"default":{"description":""}}}}}}`,
|
||||
},
|
||||
{
|
||||
name: "add response",
|
||||
@@ -36,19 +36,19 @@ func TestNewOperation(t *testing.T) {
|
||||
operation.AddResponse(200, response)
|
||||
return operation
|
||||
},
|
||||
expectedJSON: `{"components":{},"info":{"title":"test swagger title","version":"test swagger version"},"openapi":"3.0.0","paths":{"/":{"post":{"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"bar":{"maximum":15,"minimum":5,"type":"integer"},"foo":{"type":"string"}},"type":"object"}}},"description":""}}}}}}`,
|
||||
expectedJSON: `{"info":{"title":"test openapi title","version":"test openapi version"},"openapi":"3.0.0","paths":{"/":{"post":{"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"bar":{"maximum":15,"minimum":5,"type":"integer"},"foo":{"type":"string"}},"type":"object"}}},"description":""}}}}}}`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
swagger := getBaseSwagger(t)
|
||||
swagger.OpenAPI = "3.0.0"
|
||||
openapi := getBaseSwagger(t)
|
||||
openapi.OpenAPI = "3.0.0"
|
||||
operation := test.getOperation(t, NewOperation())
|
||||
|
||||
swagger.AddOperation("/", http.MethodPost, operation.Operation)
|
||||
openapi.AddOperation("/", http.MethodPost, operation.Operation)
|
||||
|
||||
data, _ := swagger.MarshalJSON()
|
||||
data, _ := openapi.MarshalJSON()
|
||||
jsonData := string(data)
|
||||
require.JSONEq(t, test.expectedJSON, jsonData, "actual json data: %s", jsonData)
|
||||
})
|
||||
|
||||
116
route.go
116
route.go
@@ -3,11 +3,13 @@ package swagger
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/alecthomas/jsonschema"
|
||||
"github.com/davidebianchi/gswagger/apirouter"
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
"github.com/invopop/jsonschema"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -22,13 +24,13 @@ var (
|
||||
)
|
||||
|
||||
// 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 apirouter.HandlerFunc, operation Operation) (interface{}, error) {
|
||||
// router also to the openapi schema, after validating it
|
||||
func (r Router[HandlerFunc, Route]) AddRawRoute(method string, routePath string, handler HandlerFunc, operation Operation) (Route, error) {
|
||||
op := operation.Operation
|
||||
if op != nil {
|
||||
err := operation.Validate(r.context)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return getZero[Route](), err
|
||||
}
|
||||
} else {
|
||||
op = openapi3.NewOperation()
|
||||
@@ -36,10 +38,12 @@ func (r Router) AddRawRoute(method string, path string, handler apirouter.Handle
|
||||
op.Responses = openapi3.NewResponses()
|
||||
}
|
||||
}
|
||||
r.swaggerSchema.AddOperation(path, method, op)
|
||||
pathWithPrefix := path.Join(r.pathPrefix, routePath)
|
||||
oasPath := r.router.TransformPathToOasPath(pathWithPrefix)
|
||||
r.swaggerSchema.AddOperation(oasPath, method, op)
|
||||
|
||||
// Handle, when content-type is json, the request/response marshalling? Maybe with a specific option.
|
||||
return r.router.AddRoute(path, method, handler), nil
|
||||
return r.router.AddRoute(method, pathWithPrefix, handler), nil
|
||||
}
|
||||
|
||||
// Content is the type of a content.
|
||||
@@ -52,28 +56,58 @@ type Schema struct {
|
||||
AllowAdditionalProperties bool
|
||||
}
|
||||
|
||||
// ParameterValue is the struct containing the schema or the content information.
|
||||
// If content is specified, it takes precedence.
|
||||
type ParameterValue map[string]struct {
|
||||
type Parameter struct {
|
||||
Content Content
|
||||
Schema *Schema
|
||||
Description string
|
||||
}
|
||||
|
||||
// ParameterValue is the struct containing the schema or the content information.
|
||||
// If content is specified, it takes precedence.
|
||||
type ParameterValue map[string]Parameter
|
||||
|
||||
// ContentValue is the struct containing the content information.
|
||||
type ContentValue struct {
|
||||
Content Content
|
||||
Description string
|
||||
}
|
||||
|
||||
type SecurityRequirements []SecurityRequirement
|
||||
type SecurityRequirement map[string][]string
|
||||
|
||||
// Definitions of the route.
|
||||
// To see how to use, refer to https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md
|
||||
type Definitions struct {
|
||||
// Specification extensions https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specification-extensions
|
||||
Extensions map[string]interface{}
|
||||
// Optional field for documentation
|
||||
Tags []string
|
||||
Summary string
|
||||
Description string
|
||||
Deprecated bool
|
||||
|
||||
// PathParams contains the path parameters. If empty is autocompleted from the path
|
||||
PathParams ParameterValue
|
||||
Querystring ParameterValue
|
||||
Headers ParameterValue
|
||||
Cookies ParameterValue
|
||||
RequestBody *ContentValue
|
||||
Responses map[int]ContentValue
|
||||
|
||||
Security SecurityRequirements
|
||||
}
|
||||
|
||||
func newOperationFromDefinition(schema Definitions) Operation {
|
||||
operation := NewOperation()
|
||||
operation.Responses = &openapi3.Responses{}
|
||||
operation.Tags = schema.Tags
|
||||
operation.Extensions = schema.Extensions
|
||||
operation.addSecurityRequirements(schema.Security)
|
||||
operation.Description = schema.Description
|
||||
operation.Summary = schema.Summary
|
||||
operation.Deprecated = schema.Deprecated
|
||||
|
||||
return operation
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -83,45 +117,45 @@ const (
|
||||
cookieParamType = "cookie"
|
||||
)
|
||||
|
||||
// AddRoute add a route with json schema inferted by passed schema.
|
||||
func (r Router) AddRoute(method string, path string, handler apirouter.HandlerFunc, schema Definitions) (interface{}, error) {
|
||||
operation := NewOperation()
|
||||
operation.Responses = make(openapi3.Responses)
|
||||
// AddRoute add a route with json schema inferred by passed schema.
|
||||
func (r Router[HandlerFunc, Route]) AddRoute(method string, path string, handler HandlerFunc, schema Definitions) (Route, error) {
|
||||
operation := newOperationFromDefinition(schema)
|
||||
|
||||
err := r.resolveRequestBodySchema(schema.RequestBody, operation)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %s", ErrRequestBody, err)
|
||||
return getZero[Route](), fmt.Errorf("%w: %s", ErrRequestBody, err)
|
||||
}
|
||||
|
||||
err = r.resolveResponsesSchema(schema.Responses, operation)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %s", ErrResponses, err)
|
||||
return getZero[Route](), fmt.Errorf("%w: %s", ErrResponses, err)
|
||||
}
|
||||
|
||||
err = r.resolveParameterSchema(pathParamsType, schema.PathParams, operation)
|
||||
oasPath := r.router.TransformPathToOasPath(path)
|
||||
err = r.resolveParameterSchema(pathParamsType, getPathParamsAutoComplete(schema, oasPath), operation)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %s", ErrPathParams, err)
|
||||
return getZero[Route](), fmt.Errorf("%w: %s", ErrPathParams, err)
|
||||
}
|
||||
|
||||
err = r.resolveParameterSchema(queryParamType, schema.Querystring, operation)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %s", ErrPathParams, err)
|
||||
return getZero[Route](), fmt.Errorf("%w: %s", ErrPathParams, err)
|
||||
}
|
||||
|
||||
err = r.resolveParameterSchema(headerParamType, schema.Headers, operation)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %s", ErrPathParams, err)
|
||||
return getZero[Route](), 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 getZero[Route](), fmt.Errorf("%w: %s", ErrPathParams, err)
|
||||
}
|
||||
|
||||
return r.AddRawRoute(method, path, handler, operation)
|
||||
}
|
||||
|
||||
func (r Router) getSchemaFromInterface(v interface{}, allowAdditionalProperties bool) (*openapi3.Schema, error) {
|
||||
func (r Router[_, _]) getSchemaFromInterface(v interface{}, allowAdditionalProperties bool) (*openapi3.Schema, error) {
|
||||
if v == nil {
|
||||
return &openapi3.Schema{}, nil
|
||||
}
|
||||
@@ -129,10 +163,11 @@ func (r Router) getSchemaFromInterface(v interface{}, allowAdditionalProperties
|
||||
reflector := &jsonschema.Reflector{
|
||||
DoNotReference: true,
|
||||
AllowAdditionalProperties: allowAdditionalProperties,
|
||||
Anonymous: true,
|
||||
}
|
||||
|
||||
jsonSchema := reflector.Reflect(v)
|
||||
jsonschema.Version = ""
|
||||
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
|
||||
@@ -151,7 +186,7 @@ func (r Router) getSchemaFromInterface(v interface{}, allowAdditionalProperties
|
||||
return schema, nil
|
||||
}
|
||||
|
||||
func (r Router) resolveRequestBodySchema(bodySchema *ContentValue, operation Operation) error {
|
||||
func (r Router[_, _]) resolveRequestBodySchema(bodySchema *ContentValue, operation Operation) error {
|
||||
if bodySchema == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -170,7 +205,7 @@ func (r Router) resolveRequestBodySchema(bodySchema *ContentValue, operation Ope
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r Router) resolveResponsesSchema(responses map[int]ContentValue, operation Operation) error {
|
||||
func (r Router[_, _]) resolveResponsesSchema(responses map[int]ContentValue, operation Operation) error {
|
||||
if responses == nil {
|
||||
operation.Responses = openapi3.NewResponses()
|
||||
}
|
||||
@@ -189,7 +224,7 @@ func (r Router) resolveResponsesSchema(responses map[int]ContentValue, operation
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r Router) resolveParameterSchema(paramType string, paramConfig ParameterValue, operation Operation) error {
|
||||
func (r Router[_, _]) resolveParameterSchema(paramType string, paramConfig ParameterValue, operation Operation) error {
|
||||
var keys = make([]string, 0, len(paramConfig))
|
||||
for k := range paramConfig {
|
||||
keys = append(keys, k)
|
||||
@@ -240,7 +275,7 @@ func (r Router) resolveParameterSchema(paramType string, paramConfig ParameterVa
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r Router) addContentToOASSchema(content Content) (openapi3.Content, error) {
|
||||
func (r Router[_, _]) addContentToOASSchema(content Content) (openapi3.Content, error) {
|
||||
oasContent := openapi3.NewContent()
|
||||
for k, v := range content {
|
||||
var err error
|
||||
@@ -252,3 +287,30 @@ func (r Router) addContentToOASSchema(content Content) (openapi3.Content, error)
|
||||
}
|
||||
return oasContent, nil
|
||||
}
|
||||
|
||||
func getPathParamsAutoComplete(schema Definitions, path string) ParameterValue {
|
||||
if schema.PathParams == nil {
|
||||
re := regexp.MustCompile(`\{([^}]+)\}`)
|
||||
segments := strings.Split(path, "/")
|
||||
for _, segment := range segments {
|
||||
params := re.FindAllStringSubmatch(segment, -1)
|
||||
if len(params) == 0 {
|
||||
continue
|
||||
}
|
||||
if schema.PathParams == nil {
|
||||
schema.PathParams = make(ParameterValue)
|
||||
}
|
||||
for _, param := range params {
|
||||
schema.PathParams[param[1]] = Parameter{
|
||||
Schema: &Schema{Value: ""},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return schema.PathParams
|
||||
}
|
||||
|
||||
func getZero[T any]() T {
|
||||
var result T
|
||||
return result
|
||||
}
|
||||
|
||||
290
route_test.go
290
route_test.go
@@ -3,12 +3,12 @@ package swagger
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/davidebianchi/gswagger/apirouter"
|
||||
"github.com/davidebianchi/gswagger/support/gorilla"
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -17,8 +17,9 @@ import (
|
||||
const jsonType = "application/json"
|
||||
const formDataType = "multipart/form-data"
|
||||
|
||||
func TestAddRoutes(t *testing.T) {
|
||||
type TestRouter = Router[gorilla.HandlerFunc, gorilla.Route]
|
||||
|
||||
func TestAddRoutes(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"`
|
||||
@@ -47,6 +48,7 @@ func TestAddRoutes(t *testing.T) {
|
||||
FirstName string `json:"firstName" jsonschema:"title=user first name"`
|
||||
LastName string `json:"lastName" jsonschema:"title=user last name"`
|
||||
Metadata interface{} `json:"metadata,omitempty" jsonschema:"title=custom properties,oneof_type=string;number"`
|
||||
UserType string `json:"userType,omitempty" jsonschema:"title=type of user,enum=simple,enum=advanced"`
|
||||
}
|
||||
|
||||
okHandler := func(w http.ResponseWriter, req *http.Request) {
|
||||
@@ -56,19 +58,19 @@ func TestAddRoutes(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
routes func(t *testing.T, router *Router)
|
||||
routes func(t *testing.T, router *TestRouter)
|
||||
fixturesPath string
|
||||
testPath string
|
||||
testMethod string
|
||||
}{
|
||||
{
|
||||
name: "no routes",
|
||||
routes: func(t *testing.T, router *Router) {},
|
||||
routes: func(t *testing.T, router *TestRouter) {},
|
||||
fixturesPath: "testdata/empty.json",
|
||||
},
|
||||
{
|
||||
name: "empty route schema",
|
||||
routes: func(t *testing.T, router *Router) {
|
||||
routes: func(t *testing.T, router *TestRouter) {
|
||||
_, err := router.AddRoute(http.MethodPost, "/", okHandler, Definitions{})
|
||||
require.NoError(t, err)
|
||||
},
|
||||
@@ -78,7 +80,7 @@ func TestAddRoutes(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "multiple real routes",
|
||||
routes: func(t *testing.T, router *Router) {
|
||||
routes: func(t *testing.T, router *TestRouter) {
|
||||
_, err := router.AddRoute(http.MethodPost, "/users", okHandler, Definitions{
|
||||
RequestBody: &ContentValue{
|
||||
Content: Content{
|
||||
@@ -128,7 +130,7 @@ func TestAddRoutes(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "multipart request body",
|
||||
routes: func(t *testing.T, router *Router) {
|
||||
routes: func(t *testing.T, router *TestRouter) {
|
||||
_, err := router.AddRoute(http.MethodPost, "/files", okHandler, Definitions{
|
||||
RequestBody: &ContentValue{
|
||||
Content: Content{
|
||||
@@ -157,7 +159,7 @@ func TestAddRoutes(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "schema with params",
|
||||
routes: func(t *testing.T, router *Router) {
|
||||
routes: func(t *testing.T, router *TestRouter) {
|
||||
var number = 0
|
||||
_, err := router.AddRoute(http.MethodGet, "/users/{userId}", okHandler, Definitions{
|
||||
PathParams: ParameterValue{
|
||||
@@ -184,9 +186,30 @@ func TestAddRoutes(t *testing.T) {
|
||||
testPath: "/users/12",
|
||||
fixturesPath: "testdata/params.json",
|
||||
},
|
||||
{
|
||||
name: "schema without explicit params autofill them",
|
||||
routes: func(t *testing.T, router *TestRouter) {
|
||||
_, err := router.AddRoute(http.MethodGet, "/users/{userId}", okHandler, Definitions{
|
||||
Querystring: ParameterValue{
|
||||
"query": {
|
||||
Schema: &Schema{Value: ""},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = router.AddRoute(http.MethodGet, "/cars/{carId}/drivers/{driverId}", okHandler, Definitions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = router.AddRoute(http.MethodGet, "/files/{name}.{extension}", okHandler, Definitions{})
|
||||
require.NoError(t, err)
|
||||
},
|
||||
testPath: "/files/myid.yaml",
|
||||
fixturesPath: "testdata/params-autofill.json",
|
||||
},
|
||||
{
|
||||
name: "schema with querystring",
|
||||
routes: func(t *testing.T, router *Router) {
|
||||
routes: func(t *testing.T, router *TestRouter) {
|
||||
_, err := router.AddRoute(http.MethodGet, "/projects", okHandler, Definitions{
|
||||
Querystring: ParameterValue{
|
||||
"projectId": {
|
||||
@@ -202,7 +225,7 @@ func TestAddRoutes(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "schema with headers",
|
||||
routes: func(t *testing.T, router *Router) {
|
||||
routes: func(t *testing.T, router *TestRouter) {
|
||||
_, err := router.AddRoute(http.MethodGet, "/projects", okHandler, Definitions{
|
||||
Headers: ParameterValue{
|
||||
"foo": {
|
||||
@@ -221,7 +244,7 @@ func TestAddRoutes(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "schema with cookies",
|
||||
routes: func(t *testing.T, router *Router) {
|
||||
routes: func(t *testing.T, router *TestRouter) {
|
||||
_, err := router.AddRoute(http.MethodGet, "/projects", okHandler, Definitions{
|
||||
Cookies: ParameterValue{
|
||||
"debug": {
|
||||
@@ -240,7 +263,7 @@ func TestAddRoutes(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "schema defined without value",
|
||||
routes: func(t *testing.T, router *Router) {
|
||||
routes: func(t *testing.T, router *TestRouter) {
|
||||
_, err := router.AddRoute(http.MethodPost, "/{id}", okHandler, Definitions{
|
||||
RequestBody: &ContentValue{
|
||||
Description: "request body without schema",
|
||||
@@ -269,7 +292,7 @@ func TestAddRoutes(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "allOf schema",
|
||||
routes: func(t *testing.T, router *Router) {
|
||||
routes: func(t *testing.T, router *TestRouter) {
|
||||
schema := openapi3.NewAllOfSchema()
|
||||
schema.AllOf = []*openapi3.SchemaRef{
|
||||
{
|
||||
@@ -314,7 +337,7 @@ func TestAddRoutes(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "anyOf schema",
|
||||
routes: func(t *testing.T, router *Router) {
|
||||
routes: func(t *testing.T, router *TestRouter) {
|
||||
schema := openapi3.NewAnyOfSchema()
|
||||
schema.AnyOf = []*openapi3.SchemaRef{
|
||||
{
|
||||
@@ -357,8 +380,8 @@ func TestAddRoutes(t *testing.T) {
|
||||
fixturesPath: "testdata/anyof.json",
|
||||
},
|
||||
{
|
||||
name: "oneOf support on properties",
|
||||
routes: func(t *testing.T, router *Router) {
|
||||
name: "oneOf and enum are supported on properties",
|
||||
routes: func(t *testing.T, router *TestRouter) {
|
||||
_, err := router.AddRoute(http.MethodPost, "/user-profile", okHandler, Definitions{
|
||||
RequestBody: &ContentValue{
|
||||
Content: Content{
|
||||
@@ -404,6 +427,77 @@ func TestAddRoutes(t *testing.T) {
|
||||
testMethod: http.MethodPost,
|
||||
fixturesPath: "testdata/oneOf.json",
|
||||
},
|
||||
{
|
||||
name: "schema with tags",
|
||||
routes: func(t *testing.T, router *TestRouter) {
|
||||
_, err := router.AddRoute(http.MethodGet, "/users", okHandler, Definitions{
|
||||
Tags: []string{"Tag1", "Tag2"},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
},
|
||||
testPath: "/users",
|
||||
fixturesPath: "testdata/tags.json",
|
||||
},
|
||||
{
|
||||
name: "schema with security",
|
||||
routes: func(t *testing.T, router *TestRouter) {
|
||||
_, err := router.AddRoute(http.MethodGet, "/users", okHandler, Definitions{
|
||||
Security: SecurityRequirements{
|
||||
SecurityRequirement{
|
||||
"api_key": []string{},
|
||||
"auth": []string{
|
||||
"resource.write",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
},
|
||||
testPath: "/users",
|
||||
fixturesPath: "testdata/security.json",
|
||||
},
|
||||
{
|
||||
name: "schema with extension",
|
||||
routes: func(t *testing.T, router *TestRouter) {
|
||||
_, err := router.AddRoute(http.MethodGet, "/users", okHandler, Definitions{
|
||||
Extensions: map[string]interface{}{
|
||||
"x-extension-field": map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
},
|
||||
testPath: "/users",
|
||||
fixturesPath: "testdata/extension.json",
|
||||
},
|
||||
{
|
||||
name: "invalid extension - not starts with x-",
|
||||
routes: func(t *testing.T, router *TestRouter) {
|
||||
_, err := router.AddRoute(http.MethodGet, "/", okHandler, Definitions{
|
||||
Extensions: map[string]interface{}{
|
||||
"extension-field": map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
})
|
||||
require.EqualError(t, err, "extra sibling fields: [extension-field]")
|
||||
},
|
||||
fixturesPath: "testdata/empty.json",
|
||||
},
|
||||
{
|
||||
name: "schema with summary, description, deprecated and operationID",
|
||||
routes: func(t *testing.T, router *TestRouter) {
|
||||
_, err := router.AddRoute(http.MethodGet, "/users", okHandler, Definitions{
|
||||
Summary: "small description",
|
||||
Description: "this is the long route description",
|
||||
Deprecated: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
},
|
||||
testPath: "/users",
|
||||
fixturesPath: "testdata/users-deprecated-with-description.json",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
@@ -411,7 +505,7 @@ func TestAddRoutes(t *testing.T) {
|
||||
context := context.Background()
|
||||
r := mux.NewRouter()
|
||||
|
||||
router, err := NewRouter(apirouter.NewGorillaMuxRouter(r), Options{
|
||||
router, err := NewRouter(gorilla.NewRouter(r), Options{
|
||||
Context: context,
|
||||
Openapi: getBaseSwagger(t),
|
||||
})
|
||||
@@ -421,7 +515,7 @@ func TestAddRoutes(t *testing.T) {
|
||||
// Add routes to test
|
||||
test.routes(t, router)
|
||||
|
||||
err = router.GenerateAndExposeSwagger()
|
||||
err = router.GenerateAndExposeOpenapi()
|
||||
require.NoError(t, err)
|
||||
|
||||
if test.testPath != "" {
|
||||
@@ -439,7 +533,7 @@ func TestAddRoutes(t *testing.T) {
|
||||
require.Equal(t, "OK", body)
|
||||
}
|
||||
|
||||
t.Run("and generate swagger documentation in json", func(t *testing.T) {
|
||||
t.Run("and generate openapi documentation in json", func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, DefaultJSONDocumentationPath, nil)
|
||||
|
||||
@@ -448,7 +542,7 @@ func TestAddRoutes(t *testing.T) {
|
||||
require.Equal(t, http.StatusOK, w.Result().StatusCode)
|
||||
|
||||
body := readBody(t, w.Result().Body)
|
||||
expected, err := ioutil.ReadFile(test.fixturesPath)
|
||||
expected, err := os.ReadFile(test.fixturesPath)
|
||||
require.NoError(t, err)
|
||||
require.JSONEq(t, string(expected), body, "actual json data: %s", body)
|
||||
})
|
||||
@@ -590,8 +684,7 @@ func TestResolveRequestBodySchema(t *testing.T) {
|
||||
"type":"object",
|
||||
"properties": {
|
||||
"id": {"type": "string"}
|
||||
},
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -602,7 +695,7 @@ func TestResolveRequestBodySchema(t *testing.T) {
|
||||
}
|
||||
|
||||
mux := mux.NewRouter()
|
||||
router, err := NewRouter(apirouter.NewGorillaMuxRouter(mux), Options{
|
||||
router, err := NewRouter(gorilla.NewRouter(mux), Options{
|
||||
Openapi: getBaseSwagger(t),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
@@ -628,6 +721,14 @@ func TestResolveResponsesSchema(t *testing.T) {
|
||||
type TestStruct struct {
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
type NestedTestStruct struct {
|
||||
Notification string `json:"notification"`
|
||||
NestedMapOfStructs map[string]TestStruct `json:"nestedMapOfStructs,omitempty"`
|
||||
}
|
||||
type ComplexTestStruct struct {
|
||||
Communication string `json:"communication"`
|
||||
MapOfStructs map[string]NestedTestStruct `json:"mapOfStructs,omitempty"`
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
responsesSchema map[int]ContentValue
|
||||
@@ -670,6 +771,79 @@ func TestResolveResponsesSchema(t *testing.T) {
|
||||
}
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "with complex schema",
|
||||
responsesSchema: map[int]ContentValue{
|
||||
200: {
|
||||
Content: Content{
|
||||
jsonType: {Value: &ComplexTestStruct{
|
||||
Communication: "myCommunication",
|
||||
MapOfStructs: map[string]NestedTestStruct{
|
||||
"myProperty": {
|
||||
Notification: "myNotification",
|
||||
NestedMapOfStructs: map[string]TestStruct{
|
||||
"myNestedProperty": {
|
||||
Message: "myMessage",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedErr: nil,
|
||||
expectedJSON: `{
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"communication": {
|
||||
"type": "string"
|
||||
},
|
||||
"mapOfStructs": {
|
||||
"additionalProperties": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"nestedMapOfStructs": {
|
||||
"additionalProperties": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"notification": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"notification"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"communication"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "with more status codes",
|
||||
responsesSchema: map[int]ContentValue{
|
||||
@@ -745,7 +919,7 @@ func TestResolveResponsesSchema(t *testing.T) {
|
||||
}
|
||||
|
||||
mux := mux.NewRouter()
|
||||
router, err := NewRouter(apirouter.NewGorillaMuxRouter(mux), Options{
|
||||
router, err := NewRouter(gorilla.NewRouter(mux), Options{
|
||||
Openapi: getBaseSwagger(t),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
@@ -753,7 +927,7 @@ func TestResolveResponsesSchema(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
operation := NewOperation()
|
||||
operation.Responses = make(openapi3.Responses)
|
||||
operation.Responses = &openapi3.Responses{}
|
||||
|
||||
err := router.resolveResponsesSchema(test.responsesSchema, operation)
|
||||
|
||||
@@ -915,7 +1089,7 @@ func TestResolveParametersSchema(t *testing.T) {
|
||||
}
|
||||
|
||||
mux := mux.NewRouter()
|
||||
router, err := NewRouter(apirouter.NewGorillaMuxRouter(mux), Options{
|
||||
router, err := NewRouter(gorilla.NewRouter(mux), Options{
|
||||
Openapi: getBaseSwagger(t),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
@@ -942,8 +1116,66 @@ func getBaseSwagger(t *testing.T) *openapi3.T {
|
||||
|
||||
return &openapi3.T{
|
||||
Info: &openapi3.Info{
|
||||
Title: "test swagger title",
|
||||
Version: "test swagger version",
|
||||
Title: "test openapi title",
|
||||
Version: "test openapi version",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPathParamsAutoComplete(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
schemaDefinition Definitions
|
||||
path string
|
||||
expected ParameterValue
|
||||
}{
|
||||
"no path params": {
|
||||
schemaDefinition: Definitions{},
|
||||
path: "/users",
|
||||
expected: nil,
|
||||
},
|
||||
"with path params": {
|
||||
schemaDefinition: Definitions{},
|
||||
path: "/users/{userId}",
|
||||
expected: ParameterValue{
|
||||
"userId": {
|
||||
Schema: &Schema{Value: ""},
|
||||
},
|
||||
},
|
||||
},
|
||||
"with multiple path params": {
|
||||
schemaDefinition: Definitions{},
|
||||
path: "/foo/{bar}.{taz}",
|
||||
expected: ParameterValue{
|
||||
"bar": {
|
||||
Schema: &Schema{Value: ""},
|
||||
},
|
||||
"taz": {
|
||||
Schema: &Schema{Value: ""},
|
||||
},
|
||||
},
|
||||
},
|
||||
"with nested multiple path params": {
|
||||
schemaDefinition: Definitions{},
|
||||
path: "/foo/{bar}.{taz}/{baz}/ok",
|
||||
expected: ParameterValue{
|
||||
"bar": {
|
||||
Schema: &Schema{Value: ""},
|
||||
},
|
||||
"taz": {
|
||||
Schema: &Schema{Value: ""},
|
||||
},
|
||||
"baz": {
|
||||
Schema: &Schema{Value: ""},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
actual := getPathParamsAutoComplete(test.schemaDefinition, test.path)
|
||||
|
||||
require.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
36
support/echo/echo.go
Normal file
36
support/echo/echo.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package echo
|
||||
|
||||
import (
|
||||
"github.com/davidebianchi/gswagger/apirouter"
|
||||
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
type Route = *echo.Route
|
||||
|
||||
type echoRouter struct {
|
||||
router *echo.Echo
|
||||
}
|
||||
|
||||
func (r echoRouter) AddRoute(method string, path string, handler echo.HandlerFunc) Route {
|
||||
return r.router.Add(method, path, handler)
|
||||
}
|
||||
|
||||
func (r echoRouter) SwaggerHandler(contentType string, blob []byte) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
c.Response().Header().Add("Content-Type", contentType)
|
||||
return c.JSONBlob(http.StatusOK, blob)
|
||||
}
|
||||
}
|
||||
|
||||
func (r echoRouter) TransformPathToOasPath(path string) string {
|
||||
return apirouter.TransformPathParamsWithColon(path)
|
||||
}
|
||||
|
||||
func NewRouter(router *echo.Echo) apirouter.Router[echo.HandlerFunc, Route] {
|
||||
return echoRouter{
|
||||
router: router,
|
||||
}
|
||||
}
|
||||
65
support/echo/echo_test.go
Normal file
65
support/echo/echo_test.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package echo
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/davidebianchi/gswagger/apirouter"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGorillaMuxRouter(t *testing.T) {
|
||||
echoRouter := echo.New()
|
||||
ar := NewRouter(echoRouter)
|
||||
|
||||
t.Run("create a new api router", func(t *testing.T) {
|
||||
require.Implements(t, (*apirouter.Router[echo.HandlerFunc, Route])(nil), ar)
|
||||
})
|
||||
|
||||
t.Run("add new route", func(t *testing.T) {
|
||||
route := ar.AddRoute(http.MethodGet, "/foo", func(c echo.Context) error {
|
||||
return c.String(http.StatusOK, "")
|
||||
})
|
||||
require.IsType(t, route, &echo.Route{})
|
||||
|
||||
t.Run("router exposes correctly api", func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest(http.MethodGet, "/foo", nil)
|
||||
|
||||
echoRouter.ServeHTTP(w, r)
|
||||
|
||||
require.Equal(t, http.StatusOK, w.Result().StatusCode)
|
||||
})
|
||||
|
||||
t.Run("router exposes api only to the specific method", func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest(http.MethodPost, "/foo", nil)
|
||||
|
||||
echoRouter.ServeHTTP(w, r)
|
||||
|
||||
require.Equal(t, http.StatusMethodNotAllowed, w.Result().StatusCode)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("create openapi handler", func(t *testing.T) {
|
||||
handlerFunc := ar.SwaggerHandler("text/html", []byte("some data"))
|
||||
echoRouter.GET("/oas", handlerFunc)
|
||||
|
||||
t.Run("responds correctly to the API", func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest(http.MethodGet, "/oas", nil)
|
||||
|
||||
echoRouter.ServeHTTP(w, r)
|
||||
|
||||
require.Equal(t, http.StatusOK, w.Result().StatusCode)
|
||||
require.Equal(t, "text/html", w.Result().Header.Get("Content-Type"))
|
||||
|
||||
body, err := io.ReadAll(w.Result().Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "some data", string(body))
|
||||
})
|
||||
})
|
||||
}
|
||||
170
support/echo/integration_test.go
Normal file
170
support/echo/integration_test.go
Normal file
@@ -0,0 +1,170 @@
|
||||
package echo_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
oasEcho "github.com/davidebianchi/gswagger/support/echo"
|
||||
|
||||
swagger "github.com/davidebianchi/gswagger"
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
swaggerOpenapiTitle = "test openapi title"
|
||||
swaggerOpenapiVersion = "test openapi version"
|
||||
)
|
||||
|
||||
type echoSwaggerRouter = swagger.Router[echo.HandlerFunc, *echo.Route]
|
||||
|
||||
func TestEchoIntegration(t *testing.T) {
|
||||
t.Run("router works correctly - echo", func(t *testing.T) {
|
||||
echoRouter, oasRouter := setupEchoSwagger(t)
|
||||
|
||||
err := oasRouter.GenerateAndExposeOpenapi()
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("/hello", func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest(http.MethodGet, "/hello", nil)
|
||||
|
||||
echoRouter.ServeHTTP(w, r)
|
||||
|
||||
require.Equal(t, http.StatusOK, w.Result().StatusCode)
|
||||
|
||||
body := readBody(t, w.Result().Body)
|
||||
require.Equal(t, "OK", body)
|
||||
})
|
||||
|
||||
t.Run("/hello/:value", func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest(http.MethodPost, "/hello/something", nil)
|
||||
|
||||
echoRouter.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, swagger.DefaultJSONDocumentationPath, nil)
|
||||
|
||||
echoRouter.ServeHTTP(w, r)
|
||||
|
||||
require.Equal(t, http.StatusOK, w.Result().StatusCode)
|
||||
|
||||
body := readBody(t, w.Result().Body)
|
||||
require.JSONEq(t, readFile(t, "../testdata/integration.json"), body)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("works correctly with subrouter - handles path prefix - echo", func(t *testing.T) {
|
||||
eRouter, oasRouter := setupEchoSwagger(t)
|
||||
|
||||
subRouter, err := oasRouter.SubRouter(oasEcho.NewRouter(eRouter), swagger.SubRouterOptions{
|
||||
PathPrefix: "/prefix",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = subRouter.AddRoute(http.MethodGet, "/foo", okHandler, swagger.Definitions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = oasRouter.GenerateAndExposeOpenapi()
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("correctly call /hello", func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest(http.MethodGet, "/hello", nil)
|
||||
|
||||
eRouter.ServeHTTP(w, r)
|
||||
|
||||
require.Equal(t, http.StatusOK, w.Result().StatusCode)
|
||||
|
||||
body := readBody(t, w.Result().Body)
|
||||
require.Equal(t, "OK", body)
|
||||
})
|
||||
|
||||
t.Run("correctly call sub router", func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest(http.MethodGet, "/prefix/foo", nil)
|
||||
|
||||
eRouter.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, swagger.DefaultJSONDocumentationPath, nil)
|
||||
|
||||
eRouter.ServeHTTP(w, r)
|
||||
|
||||
require.Equal(t, http.StatusOK, w.Result().StatusCode)
|
||||
|
||||
body := readBody(t, w.Result().Body)
|
||||
require.JSONEq(t, readFile(t, "../testdata/intergation-subrouter.json"), body, body)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func readBody(t *testing.T, requestBody io.ReadCloser) string {
|
||||
t.Helper()
|
||||
|
||||
body, err := io.ReadAll(requestBody)
|
||||
require.NoError(t, err)
|
||||
|
||||
return string(body)
|
||||
}
|
||||
|
||||
func setupEchoSwagger(t *testing.T) (*echo.Echo, *echoSwaggerRouter) {
|
||||
t.Helper()
|
||||
|
||||
context := context.Background()
|
||||
e := echo.New()
|
||||
|
||||
router, err := swagger.NewRouter(oasEcho.NewRouter(e), swagger.Options{
|
||||
Context: context,
|
||||
Openapi: &openapi3.T{
|
||||
Info: &openapi3.Info{
|
||||
Title: swaggerOpenapiTitle,
|
||||
Version: swaggerOpenapiVersion,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
operation := swagger.Operation{}
|
||||
|
||||
_, err = router.AddRawRoute(http.MethodGet, "/hello", okHandler, operation)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = router.AddRoute(http.MethodPost, "/hello/:value", okHandler, swagger.Definitions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
return e, router
|
||||
}
|
||||
|
||||
func okHandler(c echo.Context) error {
|
||||
return c.String(http.StatusOK, "OK")
|
||||
}
|
||||
|
||||
func readFile(t *testing.T, path string) string {
|
||||
t.Helper()
|
||||
|
||||
fileContent, err := os.ReadFile(path)
|
||||
require.NoError(t, err)
|
||||
|
||||
return string(fileContent)
|
||||
}
|
||||
34
support/fiber/fiber.go
Normal file
34
support/fiber/fiber.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package fiber
|
||||
|
||||
import (
|
||||
"github.com/davidebianchi/gswagger/apirouter"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
type HandlerFunc = fiber.Handler
|
||||
type Route = fiber.Router
|
||||
|
||||
type fiberRouter struct {
|
||||
router fiber.Router
|
||||
}
|
||||
|
||||
func NewRouter(router fiber.Router) apirouter.Router[HandlerFunc, Route] {
|
||||
return fiberRouter{
|
||||
router: router,
|
||||
}
|
||||
}
|
||||
|
||||
func (r fiberRouter) AddRoute(method string, path string, handler HandlerFunc) Route {
|
||||
return r.router.Add(method, path, handler)
|
||||
}
|
||||
|
||||
func (r fiberRouter) SwaggerHandler(contentType string, blob []byte) HandlerFunc {
|
||||
return func(c *fiber.Ctx) error {
|
||||
c.Set("Content-Type", contentType)
|
||||
return c.Send(blob)
|
||||
}
|
||||
}
|
||||
|
||||
func (r fiberRouter) TransformPathToOasPath(path string) string {
|
||||
return apirouter.TransformPathParamsWithColon(path)
|
||||
}
|
||||
63
support/fiber/fiber_test.go
Normal file
63
support/fiber/fiber_test.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package fiber
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/davidebianchi/gswagger/apirouter"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestFiberRouterSupport(t *testing.T) {
|
||||
fiberRouter := fiber.New()
|
||||
ar := NewRouter(fiberRouter)
|
||||
|
||||
t.Run("create a new api router", func(t *testing.T) {
|
||||
require.Implements(t, (*apirouter.Router[HandlerFunc, Route])(nil), ar)
|
||||
})
|
||||
|
||||
t.Run("add new route", func(t *testing.T) {
|
||||
route := ar.AddRoute(http.MethodGet, "/foo", func(c *fiber.Ctx) error {
|
||||
return c.SendStatus(http.StatusOK)
|
||||
})
|
||||
require.IsType(t, route, fiber.New())
|
||||
|
||||
t.Run("router exposes correctly api", func(t *testing.T) {
|
||||
r := httptest.NewRequest(http.MethodGet, "/foo", nil)
|
||||
|
||||
resp, err := fiberRouter.Test(r)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("router exposes api only to the specific method", func(t *testing.T) {
|
||||
r := httptest.NewRequest(http.MethodPost, "/foo", nil)
|
||||
|
||||
resp, err := fiberRouter.Test(r)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusMethodNotAllowed, resp.StatusCode)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("create openapi handler", func(t *testing.T) {
|
||||
handlerFunc := ar.SwaggerHandler("text/html", []byte("some data"))
|
||||
fiberRouter.Get("/oas", handlerFunc)
|
||||
|
||||
t.Run("responds correctly to the API", func(t *testing.T) {
|
||||
r := httptest.NewRequest(http.MethodGet, "/oas", nil)
|
||||
|
||||
resp, err := fiberRouter.Test(r)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
require.Equal(t, "text/html", resp.Header.Get("Content-Type"))
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "some data", string(body))
|
||||
})
|
||||
})
|
||||
}
|
||||
165
support/fiber/integration_test.go
Normal file
165
support/fiber/integration_test.go
Normal file
@@ -0,0 +1,165 @@
|
||||
package fiber_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
swagger "github.com/davidebianchi/gswagger"
|
||||
oasFiber "github.com/davidebianchi/gswagger/support/fiber"
|
||||
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type SwaggerRouter = swagger.Router[oasFiber.HandlerFunc, oasFiber.Route]
|
||||
|
||||
const (
|
||||
swaggerOpenapiTitle = "test openapi title"
|
||||
swaggerOpenapiVersion = "test openapi version"
|
||||
)
|
||||
|
||||
func TestFiberIntegration(t *testing.T) {
|
||||
t.Run("router works correctly", func(t *testing.T) {
|
||||
router, oasRouter := setupSwagger(t)
|
||||
|
||||
err := oasRouter.GenerateAndExposeOpenapi()
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("/hello", func(t *testing.T) {
|
||||
r := httptest.NewRequest(http.MethodGet, "/hello", nil)
|
||||
|
||||
resp, err := router.Test(r)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
body := readBody(t, resp.Body)
|
||||
require.Equal(t, "OK", body)
|
||||
})
|
||||
|
||||
t.Run("/hello/:value", func(t *testing.T) {
|
||||
r := httptest.NewRequest(http.MethodPost, "/hello/something", nil)
|
||||
|
||||
resp, err := router.Test(r)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
body := readBody(t, resp.Body)
|
||||
require.Equal(t, "OK", body)
|
||||
})
|
||||
|
||||
t.Run("and generate swagger", func(t *testing.T) {
|
||||
r := httptest.NewRequest(http.MethodGet, swagger.DefaultJSONDocumentationPath, nil)
|
||||
|
||||
resp, err := router.Test(r)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
body := readBody(t, resp.Body)
|
||||
require.JSONEq(t, readFile(t, "../testdata/integration.json"), body, body)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("works correctly with subrouter - handles path prefix - gorilla mux", func(t *testing.T) {
|
||||
fiberRouter, oasRouter := setupSwagger(t)
|
||||
|
||||
subRouter, err := oasRouter.SubRouter(oasFiber.NewRouter(fiberRouter), swagger.SubRouterOptions{
|
||||
PathPrefix: "/prefix",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = subRouter.AddRoute(http.MethodGet, "/foo", okHandler, swagger.Definitions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = oasRouter.GenerateAndExposeOpenapi()
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("correctly call /hello", func(t *testing.T) {
|
||||
r := httptest.NewRequest(http.MethodGet, "/hello", nil)
|
||||
|
||||
resp, err := fiberRouter.Test(r)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
body := readBody(t, resp.Body)
|
||||
require.Equal(t, "OK", body)
|
||||
})
|
||||
|
||||
t.Run("correctly call sub router", func(t *testing.T) {
|
||||
r := httptest.NewRequest(http.MethodGet, "/prefix/foo", nil)
|
||||
|
||||
resp, err := fiberRouter.Test(r)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
body := readBody(t, resp.Body)
|
||||
require.Equal(t, "OK", body)
|
||||
})
|
||||
|
||||
t.Run("and generate swagger", func(t *testing.T) {
|
||||
r := httptest.NewRequest(http.MethodGet, swagger.DefaultJSONDocumentationPath, nil)
|
||||
|
||||
resp, err := fiberRouter.Test(r)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
body := readBody(t, resp.Body)
|
||||
require.JSONEq(t, readFile(t, "../testdata/intergation-subrouter.json"), body, body)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func setupSwagger(t *testing.T) (*fiber.App, *SwaggerRouter) {
|
||||
t.Helper()
|
||||
|
||||
context := context.Background()
|
||||
fiberRouter := fiber.New()
|
||||
|
||||
router, err := swagger.NewRouter(oasFiber.NewRouter(fiberRouter), swagger.Options{
|
||||
Context: context,
|
||||
Openapi: &openapi3.T{
|
||||
Info: &openapi3.Info{
|
||||
Title: swaggerOpenapiTitle,
|
||||
Version: swaggerOpenapiVersion,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
operation := swagger.Operation{}
|
||||
|
||||
_, err = router.AddRawRoute(http.MethodGet, "/hello", okHandler, operation)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = router.AddRoute(http.MethodPost, "/hello/:value", okHandler, swagger.Definitions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
return fiberRouter, router
|
||||
}
|
||||
|
||||
func okHandler(c *fiber.Ctx) error {
|
||||
c.Status(http.StatusOK)
|
||||
return c.SendString("OK")
|
||||
}
|
||||
|
||||
func readBody(t *testing.T, requestBody io.ReadCloser) string {
|
||||
t.Helper()
|
||||
|
||||
body, err := io.ReadAll(requestBody)
|
||||
require.NoError(t, err)
|
||||
|
||||
return string(body)
|
||||
}
|
||||
|
||||
func readFile(t *testing.T, path string) string {
|
||||
t.Helper()
|
||||
|
||||
fileContent, err := os.ReadFile(path)
|
||||
require.NoError(t, err)
|
||||
|
||||
return string(fileContent)
|
||||
}
|
||||
118
support/gorilla/examples_test.go
Normal file
118
support/gorilla/examples_test.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package gorilla_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
swagger "github.com/davidebianchi/gswagger"
|
||||
"github.com/davidebianchi/gswagger/support/gorilla"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func TestExample(t *testing.T) {
|
||||
context := context.Background()
|
||||
muxRouter := mux.NewRouter()
|
||||
|
||||
router, _ := swagger.NewRouter(gorilla.NewRouter(muxRouter), swagger.Options{
|
||||
Context: context,
|
||||
Openapi: &openapi3.T{
|
||||
Info: &openapi3.Info{
|
||||
Title: "my title",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
okHandler := func(w http.ResponseWriter, req *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("OK"))
|
||||
}
|
||||
|
||||
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 errorResponse struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
router.AddRoute(http.MethodPost, "/users", okHandler, swagger.Definitions{
|
||||
RequestBody: &swagger.ContentValue{
|
||||
Content: swagger.Content{
|
||||
"application/json": {Value: User{}},
|
||||
},
|
||||
},
|
||||
Responses: map[int]swagger.ContentValue{
|
||||
201: {
|
||||
Content: swagger.Content{
|
||||
"text/html": {Value: ""},
|
||||
},
|
||||
},
|
||||
401: {
|
||||
Content: swagger.Content{
|
||||
"application/json": {Value: &errorResponse{}},
|
||||
},
|
||||
Description: "invalid request",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
router.AddRoute(http.MethodGet, "/users", okHandler, swagger.Definitions{
|
||||
Responses: map[int]swagger.ContentValue{
|
||||
200: {
|
||||
Content: swagger.Content{
|
||||
"application/json": {Value: &[]User{}},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
carSchema := openapi3.NewObjectSchema().WithProperties(map[string]*openapi3.Schema{
|
||||
"foo": openapi3.NewStringSchema(),
|
||||
"bar": openapi3.NewIntegerSchema().WithMax(15).WithMin(5),
|
||||
})
|
||||
requestBody := openapi3.NewRequestBody().WithJSONSchema(carSchema)
|
||||
operation := swagger.NewOperation()
|
||||
operation.AddRequestBody(requestBody)
|
||||
|
||||
router.AddRawRoute(http.MethodPost, "/cars", okHandler, operation)
|
||||
|
||||
_, err := router.AddRoute(http.MethodGet, "/users/{userId}", okHandler, swagger.Definitions{
|
||||
Querystring: swagger.ParameterValue{
|
||||
"query": {
|
||||
Schema: &swagger.Schema{Value: ""},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = router.AddRoute(http.MethodGet, "/cars/{carId}/drivers/{driverId}", okHandler, swagger.Definitions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
router.GenerateAndExposeOpenapi()
|
||||
|
||||
t.Run("correctly exposes documentation", func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, swagger.DefaultJSONDocumentationPath, nil)
|
||||
|
||||
muxRouter.ServeHTTP(w, req)
|
||||
|
||||
require.Equal(t, http.StatusOK, w.Result().StatusCode)
|
||||
require.Equal(t, "application/json", w.Result().Header.Get("Content-Type"))
|
||||
|
||||
body, err := io.ReadAll(w.Result().Body)
|
||||
require.NoError(t, err)
|
||||
expected, err := os.ReadFile("./testdata/examples-users.json")
|
||||
require.NoError(t, err)
|
||||
require.JSONEq(t, string(expected), string(body), "actual json data: %s", body)
|
||||
})
|
||||
}
|
||||
39
support/gorilla/gorilla.go
Normal file
39
support/gorilla/gorilla.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package gorilla
|
||||
|
||||
import (
|
||||
"github.com/davidebianchi/gswagger/apirouter"
|
||||
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// HandlerFunc is the http type handler used by gorilla/mux
|
||||
type HandlerFunc func(w http.ResponseWriter, req *http.Request)
|
||||
type Route = *mux.Route
|
||||
|
||||
type gorillaRouter struct {
|
||||
router *mux.Router
|
||||
}
|
||||
|
||||
func (r gorillaRouter) AddRoute(method string, path string, handler HandlerFunc) Route {
|
||||
return r.router.HandleFunc(path, handler).Methods(method)
|
||||
}
|
||||
|
||||
func (r gorillaRouter) SwaggerHandler(contentType string, blob []byte) HandlerFunc {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(blob)
|
||||
}
|
||||
}
|
||||
|
||||
func (r gorillaRouter) TransformPathToOasPath(path string) string {
|
||||
return path
|
||||
}
|
||||
|
||||
func NewRouter(router *mux.Router) apirouter.Router[HandlerFunc, Route] {
|
||||
return gorillaRouter{
|
||||
router: router,
|
||||
}
|
||||
}
|
||||
66
support/gorilla/gorilla_test.go
Normal file
66
support/gorilla/gorilla_test.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package gorilla
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/davidebianchi/gswagger/apirouter"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGorillaMuxRouter(t *testing.T) {
|
||||
muxRouter := mux.NewRouter()
|
||||
ar := NewRouter(muxRouter)
|
||||
|
||||
t.Run("create a new api router", func(t *testing.T) {
|
||||
require.Implements(t, (*apirouter.Router[HandlerFunc, Route])(nil), ar)
|
||||
})
|
||||
|
||||
t.Run("add new route", func(t *testing.T) {
|
||||
route := ar.AddRoute(http.MethodGet, "/foo", func(w http.ResponseWriter, req *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
w.Write(nil)
|
||||
})
|
||||
require.IsType(t, route, &mux.Route{})
|
||||
|
||||
t.Run("router exposes correctly api", func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest(http.MethodGet, "/foo", nil)
|
||||
|
||||
muxRouter.ServeHTTP(w, r)
|
||||
|
||||
require.Equal(t, http.StatusOK, w.Result().StatusCode)
|
||||
})
|
||||
|
||||
t.Run("router exposes api only to the specific method", func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest(http.MethodPost, "/foo", nil)
|
||||
|
||||
muxRouter.ServeHTTP(w, r)
|
||||
|
||||
require.Equal(t, http.StatusMethodNotAllowed, w.Result().StatusCode)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("create openapi handler", func(t *testing.T) {
|
||||
handlerFunc := ar.SwaggerHandler("text/html", []byte("some data"))
|
||||
muxRouter.HandleFunc("/oas", handlerFunc).Methods(http.MethodGet)
|
||||
|
||||
t.Run("responds correctly to the API", func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest(http.MethodGet, "/oas", nil)
|
||||
|
||||
muxRouter.ServeHTTP(w, r)
|
||||
|
||||
require.Equal(t, http.StatusOK, w.Result().StatusCode)
|
||||
require.Equal(t, "text/html", w.Result().Header.Get("Content-Type"))
|
||||
|
||||
body, err := io.ReadAll(w.Result().Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "some data", string(body))
|
||||
})
|
||||
})
|
||||
}
|
||||
157
support/gorilla/integration_test.go
Normal file
157
support/gorilla/integration_test.go
Normal file
@@ -0,0 +1,157 @@
|
||||
package gorilla_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
swagger "github.com/davidebianchi/gswagger"
|
||||
"github.com/davidebianchi/gswagger/support/gorilla"
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
swaggerOpenapiTitle = "test openapi title"
|
||||
swaggerOpenapiVersion = "test openapi version"
|
||||
)
|
||||
|
||||
type SwaggerRouter = swagger.Router[gorilla.HandlerFunc, gorilla.Route]
|
||||
|
||||
func TestGorillaIntegration(t *testing.T) {
|
||||
t.Run("router works correctly", func(t *testing.T) {
|
||||
muxRouter, oasRouter := setupSwagger(t)
|
||||
|
||||
err := oasRouter.GenerateAndExposeOpenapi()
|
||||
require.NoError(t, err)
|
||||
|
||||
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, swagger.DefaultJSONDocumentationPath, nil)
|
||||
|
||||
muxRouter.ServeHTTP(w, r)
|
||||
|
||||
require.Equal(t, http.StatusOK, w.Result().StatusCode)
|
||||
|
||||
body := readBody(t, w.Result().Body)
|
||||
require.JSONEq(t, readFile(t, "../testdata/integration.json"), body)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("works correctly with subrouter - handles path prefix - gorilla mux", func(t *testing.T) {
|
||||
muxRouter, oasRouter := setupSwagger(t)
|
||||
|
||||
muxSubRouter := muxRouter.NewRoute().Subrouter()
|
||||
subRouter, err := oasRouter.SubRouter(gorilla.NewRouter(muxSubRouter), swagger.SubRouterOptions{
|
||||
PathPrefix: "/prefix",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = subRouter.AddRoute(http.MethodGet, "/foo", okHandler, swagger.Definitions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = oasRouter.GenerateAndExposeOpenapi()
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("correctly call router", func(t *testing.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("correctly call sub router", func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest(http.MethodGet, "/prefix/foo", 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, swagger.DefaultJSONDocumentationPath, nil)
|
||||
|
||||
muxRouter.ServeHTTP(w, r)
|
||||
|
||||
require.Equal(t, http.StatusOK, w.Result().StatusCode)
|
||||
|
||||
body := readBody(t, w.Result().Body)
|
||||
require.JSONEq(t, readFile(t, "../testdata/intergation-subrouter.json"), body, body)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func readBody(t *testing.T, requestBody io.ReadCloser) string {
|
||||
t.Helper()
|
||||
|
||||
body, err := io.ReadAll(requestBody)
|
||||
require.NoError(t, err)
|
||||
|
||||
return string(body)
|
||||
}
|
||||
|
||||
func setupSwagger(t *testing.T) (*mux.Router, *SwaggerRouter) {
|
||||
t.Helper()
|
||||
|
||||
context := context.Background()
|
||||
muxRouter := mux.NewRouter()
|
||||
|
||||
router, err := swagger.NewRouter(gorilla.NewRouter(muxRouter), swagger.Options{
|
||||
Context: context,
|
||||
Openapi: &openapi3.T{
|
||||
Info: &openapi3.Info{
|
||||
Title: swaggerOpenapiTitle,
|
||||
Version: swaggerOpenapiVersion,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
operation := swagger.Operation{}
|
||||
|
||||
_, err = router.AddRawRoute(http.MethodGet, "/hello", okHandler, operation)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = router.AddRoute(http.MethodPost, "/hello/{value}", okHandler, swagger.Definitions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
return muxRouter, router
|
||||
}
|
||||
|
||||
func okHandler(w http.ResponseWriter, req *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(`OK`))
|
||||
}
|
||||
|
||||
func readFile(t *testing.T, path string) string {
|
||||
t.Helper()
|
||||
|
||||
fileContent, err := os.ReadFile(path)
|
||||
require.NoError(t, err)
|
||||
|
||||
return string(fileContent)
|
||||
}
|
||||
185
support/gorilla/testdata/examples-users.json
vendored
Normal file
185
support/gorilla/testdata/examples-users.json
vendored
Normal file
@@ -0,0 +1,185 @@
|
||||
{
|
||||
"info": {
|
||||
"title": "my title",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"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": {
|
||||
"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": {
|
||||
"text/html": {
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": ""
|
||||
},
|
||||
"401": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"message"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "invalid request"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/users/{userId}": {
|
||||
"get": {
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "userId",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"default": {
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
37
support/testdata/integration.json
vendored
Normal file
37
support/testdata/integration.json
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"info": {
|
||||
"title": "test openapi title",
|
||||
"version": "test openapi version"
|
||||
},
|
||||
"openapi": "3.0.0",
|
||||
"paths": {
|
||||
"/hello": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"default": {
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/hello/{value}": {
|
||||
"post": {
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "value",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"default": {
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
46
support/testdata/intergation-subrouter.json
vendored
Normal file
46
support/testdata/intergation-subrouter.json
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"info": {
|
||||
"title": "test openapi title",
|
||||
"version": "test openapi version"
|
||||
},
|
||||
"openapi": "3.0.0",
|
||||
"paths": {
|
||||
"/hello": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"default": {
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/hello/{value}": {
|
||||
"post": {
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "value",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"default": {
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/prefix/foo": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"default": {
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
5
testdata/allof.json
vendored
5
testdata/allof.json
vendored
@@ -1,8 +1,7 @@
|
||||
{
|
||||
"components": {},
|
||||
"info": {
|
||||
"title": "test swagger title",
|
||||
"version": "test swagger version"
|
||||
"title": "test openapi title",
|
||||
"version": "test openapi version"
|
||||
},
|
||||
"openapi": "3.0.0",
|
||||
"paths": {
|
||||
|
||||
5
testdata/anyof.json
vendored
5
testdata/anyof.json
vendored
@@ -1,8 +1,7 @@
|
||||
{
|
||||
"components": {},
|
||||
"info": {
|
||||
"title": "test swagger title",
|
||||
"version": "test swagger version"
|
||||
"title": "test openapi title",
|
||||
"version": "test openapi version"
|
||||
},
|
||||
"openapi": "3.0.0",
|
||||
"paths": {
|
||||
|
||||
5
testdata/cookies.json
vendored
5
testdata/cookies.json
vendored
@@ -1,8 +1,7 @@
|
||||
{
|
||||
"components": {},
|
||||
"info": {
|
||||
"title": "test swagger title",
|
||||
"version": "test swagger version"
|
||||
"title": "test openapi title",
|
||||
"version": "test openapi version"
|
||||
},
|
||||
"openapi": "3.0.0",
|
||||
"paths": {
|
||||
|
||||
5
testdata/empty-route-schema.json
vendored
5
testdata/empty-route-schema.json
vendored
@@ -1,8 +1,7 @@
|
||||
{
|
||||
"components": {},
|
||||
"info": {
|
||||
"title": "test swagger title",
|
||||
"version": "test swagger version"
|
||||
"title": "test openapi title",
|
||||
"version": "test openapi version"
|
||||
},
|
||||
"openapi": "3.0.0",
|
||||
"paths": {
|
||||
|
||||
5
testdata/empty.json
vendored
5
testdata/empty.json
vendored
@@ -1,8 +1,7 @@
|
||||
{
|
||||
"components": {},
|
||||
"info": {
|
||||
"title": "test swagger title",
|
||||
"version": "test swagger version"
|
||||
"title": "test openapi title",
|
||||
"version": "test openapi version"
|
||||
},
|
||||
"openapi": "3.0.0",
|
||||
"paths": {}
|
||||
|
||||
21
testdata/extension.json
vendored
Normal file
21
testdata/extension.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"info": {
|
||||
"title": "test openapi title",
|
||||
"version": "test openapi version"
|
||||
},
|
||||
"openapi": "3.0.0",
|
||||
"paths": {
|
||||
"/users": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"default": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"x-extension-field": {
|
||||
"foo": "bar"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
5
testdata/headers.json
vendored
5
testdata/headers.json
vendored
@@ -1,8 +1,7 @@
|
||||
{
|
||||
"components": {},
|
||||
"info": {
|
||||
"title": "test swagger title",
|
||||
"version": "test swagger version"
|
||||
"title": "test openapi title",
|
||||
"version": "test openapi version"
|
||||
},
|
||||
"openapi": "3.0.0",
|
||||
"paths": {
|
||||
|
||||
7
testdata/multipart-requestbody.json
vendored
7
testdata/multipart-requestbody.json
vendored
@@ -1,8 +1,7 @@
|
||||
{
|
||||
"components": {},
|
||||
"info": {
|
||||
"title": "test swagger title",
|
||||
"version": "test swagger version"
|
||||
"title": "test openapi title",
|
||||
"version": "test openapi version"
|
||||
},
|
||||
"openapi": "3.0.0",
|
||||
"paths": {
|
||||
@@ -14,13 +13,11 @@
|
||||
"multipart/form-data": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"address": {
|
||||
"additionalProperties": true,
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"street": {
|
||||
|
||||
11
testdata/oneOf.json
vendored
11
testdata/oneOf.json
vendored
@@ -1,8 +1,7 @@
|
||||
{
|
||||
"components": {},
|
||||
"info": {
|
||||
"title": "test swagger title",
|
||||
"version": "test swagger version"
|
||||
"title": "test openapi title",
|
||||
"version": "test openapi version"
|
||||
},
|
||||
"openapi": "3.0.0",
|
||||
"paths": {
|
||||
@@ -70,7 +69,6 @@
|
||||
"type": "string"
|
||||
},
|
||||
"metadata": {
|
||||
"additionalProperties": true,
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string"
|
||||
@@ -80,6 +78,11 @@
|
||||
}
|
||||
],
|
||||
"title": "custom properties"
|
||||
},
|
||||
"userType": {
|
||||
"title": "type of user",
|
||||
"type": "string",
|
||||
"enum": ["simple", "advanced"]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
||||
89
testdata/params-autofill.json
vendored
Normal file
89
testdata/params-autofill.json
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
{
|
||||
"info": {
|
||||
"title": "test openapi title",
|
||||
"version": "test openapi 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": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "userId",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"default": {
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/files/{name}.{extension}": {
|
||||
"get": {
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "extension",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "path",
|
||||
"name": "name",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"default": {
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
5
testdata/params.json
vendored
5
testdata/params.json
vendored
@@ -1,8 +1,7 @@
|
||||
{
|
||||
"components": {},
|
||||
"info": {
|
||||
"title": "test swagger title",
|
||||
"version": "test swagger version"
|
||||
"title": "test openapi title",
|
||||
"version": "test openapi version"
|
||||
},
|
||||
"openapi": "3.0.0",
|
||||
"paths": {
|
||||
|
||||
5
testdata/query.json
vendored
5
testdata/query.json
vendored
@@ -1,8 +1,7 @@
|
||||
{
|
||||
"components": {},
|
||||
"info": {
|
||||
"title": "test swagger title",
|
||||
"version": "test swagger version"
|
||||
"title": "test openapi title",
|
||||
"version": "test openapi version"
|
||||
},
|
||||
"openapi": "3.0.0",
|
||||
"paths": {
|
||||
|
||||
18
testdata/router_with_prefix.json
vendored
Normal file
18
testdata/router_with_prefix.json
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"info": {
|
||||
"title": "test openapi title",
|
||||
"version": "test openapi version"
|
||||
},
|
||||
"openapi": "3.0.0",
|
||||
"paths": {
|
||||
"/prefix/foo": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"default": {
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
6
testdata/schema-no-content.json
vendored
6
testdata/schema-no-content.json
vendored
@@ -1,8 +1,7 @@
|
||||
{
|
||||
"components": {},
|
||||
"info": {
|
||||
"title": "test swagger title",
|
||||
"version": "test swagger version"
|
||||
"title": "test openapi title",
|
||||
"version": "test openapi version"
|
||||
},
|
||||
"openapi": "3.0.0",
|
||||
"paths": {
|
||||
@@ -32,6 +31,7 @@
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {},
|
||||
"description": "request body without schema"
|
||||
},
|
||||
"responses": {
|
||||
|
||||
26
testdata/security.json
vendored
Normal file
26
testdata/security.json
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"info": {
|
||||
"title": "test openapi title",
|
||||
"version": "test openapi version"
|
||||
},
|
||||
"openapi": "3.0.0",
|
||||
"paths": {
|
||||
"/users": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"default": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"api_key": [],
|
||||
"auth": [
|
||||
"resource.write"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
36
testdata/subrouter.json
vendored
Normal file
36
testdata/subrouter.json
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"info": {
|
||||
"title": "test openapi title",
|
||||
"version": "test openapi version"
|
||||
},
|
||||
"openapi": "3.0.0",
|
||||
"paths": {
|
||||
"/foo": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"default": {
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/prefix/taz": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"default": {
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/prefix": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"default": {
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
19
testdata/tags.json
vendored
Normal file
19
testdata/tags.json
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"info": {
|
||||
"title": "test openapi title",
|
||||
"version": "test openapi version"
|
||||
},
|
||||
"openapi": "3.0.0",
|
||||
"paths": {
|
||||
"/users": {
|
||||
"get": {
|
||||
"tags": ["Tag1", "Tag2"],
|
||||
"responses": {
|
||||
"default": {
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
21
testdata/users-deprecated-with-description.json
vendored
Normal file
21
testdata/users-deprecated-with-description.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"info": {
|
||||
"title": "test openapi title",
|
||||
"version": "test openapi version"
|
||||
},
|
||||
"openapi": "3.0.0",
|
||||
"paths": {
|
||||
"/users": {
|
||||
"get": {
|
||||
"deprecated": true,
|
||||
"description": "this is the long route description",
|
||||
"responses": {
|
||||
"default": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"summary": "small description"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
5
testdata/users_employees.json
vendored
5
testdata/users_employees.json
vendored
@@ -1,8 +1,7 @@
|
||||
{
|
||||
"components": {},
|
||||
"info": {
|
||||
"title": "test swagger title",
|
||||
"version": "test swagger version"
|
||||
"title": "test openapi title",
|
||||
"version": "test openapi version"
|
||||
},
|
||||
"openapi": "3.0.0",
|
||||
"paths": {
|
||||
|
||||
5
testdata/users_employees.yaml
vendored
5
testdata/users_employees.yaml
vendored
@@ -1,7 +1,6 @@
|
||||
components: {}
|
||||
info:
|
||||
title: test swagger title
|
||||
version: test swagger version
|
||||
title: test openapi title
|
||||
version: test openapi version
|
||||
openapi: 3.0.0
|
||||
paths:
|
||||
/employees:
|
||||
|
||||
Reference in New Issue
Block a user