Giter Site home page Giter Site logo

long2ice / swagin Goto Github PK

View Code? Open in Web Editor NEW
66.0 4.0 18.0 493 KB

Swagger + Gin = SwaGin, a web framework based on Gin and Swagger

Home Page: https://swagin.long2ice.io/docs

License: Apache License 2.0

Go 93.97% HTML 3.59% Dockerfile 2.44%
gin swagger fastapi api golang swagger-ui openapi redoc

swagin's Introduction

Swagger + Gin = SwaGin

deploy Go Reference

If you use Fiber, you can try my another similar project Fibers.

Introduction

SwaGin is a web framework based on Gin and Swagger, which wraps Gin and provides built-in swagger api docs and request model validation.

Why I build this project?

Previous I have used FastAPI, which gives me a great experience in api docs generation, because nobody like writing api docs.

Now I use Gin but I can't found anything like that, I found swag but which write docs with comment is so stupid. So there is SwaGin.

Installation

go get -u github.com/long2ice/swagin

Online Demo

You can see online demo at https://swagin.long2ice.io/docs or https://swagin.long2ice.io/redoc.

And you can reference all usage in examples.

Usage

Build Swagger

Firstly, build a swagger object with basic information.

package examples

import (
  "github.com/getkin/kin-openapi/openapi3"
  "github.com/long2ice/swagin/swagger"
)

func NewSwagger() *swagger.Swagger {
  return swagger.New("SwaGin", "Swagger + Gin = SwaGin", "0.1.0",
    swagger.License(&openapi3.License{
      Name: "Apache License 2.0",
      URL:  "https://github.com/long2ice/swagin/blob/dev/LICENSE",
    }),
    swagger.Contact(&openapi3.Contact{
      Name:  "long2ice",
      URL:   "https://github.com/long2ice",
      Email: "[email protected]",
    }),
    swagger.TermsOfService("https://github.com/long2ice"),
  )
}

Write API

Then write a router function.

package examples

type TestQuery struct {
  Name string `query:"name" validate:"required" json:"name" description:"name of model" default:"test"`
}

func TestQuery(c *gin.Context, req TestQueryReq) error {
  return c.JSON(req)
}

// TestQueryNoReq if there is no req body and query
func TestQueryNoReq(c *gin.Context) {
  c.JSON(http.StatusOK, "{}")
}

Note that the attributes in TestQuery? SwaGin will validate request and inject it automatically, then you can use it in handler easily.

Write Router

Then write router with some docs configuration and api.

package examples

var query = router.New(
  TestQuery,
  router.Summary("Test Query"),
  router.Description("Test Query Model"),
  router.Tags("Test"),
)

// if there is no req body, you need use router.NewX
var query = router.NewX(
  TestQueryNoReq,
  router.Summary("Test Query"),
  router.Description("Test Query Model"),
  router.Tags("Test"),
)

Security

If you want to project your api with a security policy, you can use security, also they will be shown in swagger docs.

Current there is five kinds of security policies.

  • Basic
  • Bearer
  • ApiKey
  • OpenID
  • OAuth2
package main

var query = router.New(
  TestQuery,
  router.Summary("Test query"),
  router.Description("Test query model"),
  router.Security(&security.Basic{}),
)

Then you can get the authentication string by context.MustGet(security.Credentials) depending on your auth type.

package main

func TestQuery(c *gin.Context) {
  user := c.MustGet(security.Credentials).(*security.User)
  fmt.Println(user)
  c.JSON(http.StatusOK, t)
}

Mount Router

Then you can mount router in your application or group.

package main

func main() {
  app := swagin.New(NewSwagger())
  queryGroup := app.Group("/query", swagin.Tags("Query"))
  queryGroup.GET("", query)
  queryGroup.GET("/:id", queryPath)
  queryGroup.DELETE("", query)
  app.GET("/noModel", noModel)
}

Start APP

Finally, start the application with routes defined.

package main

import (
  "github.com/gin-contrib/cors"
  "github.com/long2ice/swagin"
)

func main() {
  app := swagin.New(NewSwagger())
  app.Use(cors.New(cors.Config{
    AllowOrigins:     []string{"*"},
    AllowMethods:     []string{"*"},
    AllowHeaders:     []string{"*"},
    AllowCredentials: true,
  }))

  queryGroup := app.Group("/query", swagin.Tags("Query"))
  queryGroup.GET("", query)
  queryGroup.GET("/:id", queryPath)
  queryGroup.DELETE("", query)

  formGroup := app.Group("/form", swagin.Tags("Form"))
  formGroup.POST("/encoded", formEncode)
  formGroup.PUT("", body)

  app.GET("/noModel", noModel)
  app.POST("/body", body)
  if err := app.Run(); err != nil {
    panic(err)
  }
}

That's all! Now you can visit http://127.0.0.1:8080/docs or http://127.0.0.1:8080/redoc to see the api docs. Have fun!

Disable Docs

In some cases you may want to disable docs such as in production, just put nil to swagin.New.

app = swagin.New(nil)

SubAPP Mount

If you want to use sub application, you can mount another SwaGin instance to main application, and their swagger docs is also separate.

package main

func main() {
  app := swagin.New(NewSwagger())
  subApp := swagin.New(NewSwagger())
  subApp.GET("/noModel", noModel)
  app.Mount("/sub", subApp)
}

Integration Tests

First install Venom at https://github.com/intercloud/venom/releases. Then you can run integration tests as follows:

$ cd examples
$ go test

This will start example application and run integration tests in directory examples/test against it.

You can also run integration tests on running example application with:

$ cd examples
$ go build
$ ./examples &
$ PID=$!
$ venom run test/*.yml
$ kill $PID

ThanksTo

  • kin-openapi, OpenAPI 3.0 implementation for Go (parsing, converting, validation, and more).
  • Gin, an HTTP web framework written in Go (Golang).

License

This project is licensed under the Apache-2.0 License.

swagin's People

Contributors

long2ice avatar

Stargazers

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

Watchers

 avatar  avatar  avatar  avatar

swagin's Issues

go get failed with gin v1.7.4

❯ go get -u github.com/long2ice/swagin
go: downloading github.com/long2ice/swagin v0.1.0
go: downloading github.com/getkin/kin-openapi v0.72.0
go: downloading github.com/getkin/kin-openapi v0.81.0
go: downloading github.com/ghodss/yaml v1.0.0
go: downloading github.com/go-openapi/swag v0.19.5
go: downloading github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e
# github.com/long2ice/swagin/router
../../go/pkg/mod/github.com/long2ice/[email protected]/router/router.go:31:14: c.ShouldBindRequest undefined (type *gin.Context has no field or method ShouldBindRequest)

Wrong Status Code Description

Status code description is duplicated on all codes as shown in screenshot below:

Capture d’écran_2022-02-08_11-20-04

Router is defined as follows:

var DeployProject = router.New(
	&DeployProjectInput{},
	router.Summary("Deploy Project"),
	router.Description("Deploy project for given configuration"),
	router.Responses(router.Response{
		"204": router.ResponseItem{
			Model:       model.Created{},
			Description: "Project created",
		},
		"400": router.ResponseItem{
			Model:       model.BadRequest{},
			Description: "Bad request",
		},
		"404": router.ResponseItem{
			Model:       model.NotFound{},
			Description: "Not found",
		},
		"500": router.ResponseItem{
			Model:       model.InternalServerError{},
			Description: "Error response",
		},
	}),
)

Missing header

In following example:

package main

import (
	"github.com/gin-gonic/gin"
	"github.com/long2ice/swagin"
	"github.com/long2ice/swagin/router"
	"github.com/long2ice/swagin/swagger"
)

func main() {
	app := swagin.New(swagger.New("SwaGin", "Swagger + Gin = SwaGin", "0.1.0"))
	app.GET("/", test)
	if err := app.Run(); err != nil {
		panic(err)
	}
}

var test = router.New(
	&Test{},
	router.Summary("Test query"),
	router.Description("Test query model"),
)

type Test struct{
	Authorization string `header:"authorization" binding:"required" json:"authorization" default:"authorization"`
	Token string `header:"token" binding:"required" json:"token" default:"token"`
}

func (t *Test) Handler(ctx *gin.Context) {
	println(">>>>>>>>>> authorization:", t.Authorization)
	println(">>>>>>>>>> token:", t.Token)
}

I require two headers in request (Token and Authorization). They are both listed in documentation (at URL /docs) and I can set them to perform request. But validation fails for header Authorization which is missing, while Token is sent and received. Furthermore, in CURL example request Authorization header is missing while Token is set.

In my opinion, both headers should be sent. Why is Authorization missing?

This seems to be a collision with authorization bechanism in SwaGin, as it works fine with other headers.

Crash on response struct with a map

Let's consider following example code:

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/long2ice/swagin"
	"github.com/long2ice/swagin/router"
	"github.com/long2ice/swagin/swagger"
)

func main() {
	app := swagin.New(swagger.New("SwaGin", "Swagger + Gin = SwaGin", "0.1.0"))
	app.GET("/", test)
	if err := app.Run(); err != nil {
		panic(err)
	}
}

var test = router.New(
	&Input{},
	router.Summary("Test query"),
	router.Description("Test query model"),
	router.Responses(router.Response{
		"200": router.ResponseItem{
			Model:       Response{},
			Description: "Bug",
		},
	}),
)

type Input struct{}

type Response struct {
	Field map[string]string
}

func (t *Input) Handler(ctx *gin.Context) {
	ctx.JSON(http.StatusOK, Response{})
}

If you run this code, il will crash in an infinite loop.

Crash when modifying request

This bug is related to the request value that is shared among all the requests. This crashed application when the request value is modified as in the example code. This is demonstrated in example application in the archive.

To run test, you must install test tool Hey. Then you can run application with make run and run test with make test.

The application will crash with a message like this:

fatal error: concurrent map iteration and map write

This is due to the fact that the request value is shared among all the requests. The request value is modified in the handler function and this causes the crash.

test.zip

x-codeSamples integration

This is more a question than an actual issue, but I think it is important to provide code samples to distribute the API docs directly in production, is there a way to use code sample objects? if anyone can point me in the right direction I can work in this functionality, as the official docs this object works like this in the API definition:

Redocly Documentation

openapi: '3.0'
info: ...
tags: [...]
paths:
  /example:
    get:
      summary: Example summary
      description: Example description
      operationId: examplePath
      responses: [...]
      parameters: [...]
      x-codeSamples:
        - lang: 'cURL'
          label: 'CLI'
          source: |
            curl --request POST \
            --url 'https://data.apiexample.com/api/example/batch_query/json?format=json' \
            --header 'content-type: application/octet-stream: ' \
            --data '{}'

hot reload option

Quick question, is there a way to enable a "hot reload" kind of option? I'd read that gin run main.go --all does that trick when using the gin package directly, but I was not able to get that to work inside swagin

I'm very new at Go and this package is helping me a lot moving from python (FastAPI) to Go, great work!!!

Tag a new release?

It seems that swagin is stable for a while. Any plans to create a new tag to works friendly with Go module or other version control toolkits?

Handlers ordering

In following example:

package main

import (
	"log"

	"github.com/gin-gonic/gin"
	"github.com/long2ice/swagin"
	"github.com/long2ice/swagin/router"
	"github.com/long2ice/swagin/swagger"
)

func main() {
	app := swagin.New(NewSwagger())
	group := app.Group("", swagin.Handlers(GroupHandler))
	group.GET("/", test)
	if err := app.Run(); err != nil {
		panic(err)
	}
}

func GroupHandler(ctx *gin.Context) {
	log.Println(">>> in group handler")
}

func RouterHandler(ctx *gin.Context) {
	log.Println(">>> in router handler")
}

var test = router.New(
	&Test{},
	router.Summary("Test query"),
	router.Description("Test query model"),
	router.Handlers(RouterHandler),
)

type Test struct{}

func (t *Test) Handler(ctx *gin.Context) {
	log.Println(">>> in handler")
}

func NewSwagger() *swagger.Swagger {
	return swagger.New("SwaGin", "Swagger + Gin = SwaGin", "0.1.0")
}

You would expect following ordering for handlers:

  1. Group handler
  2. Router handler
  3. The hanlder

But when you run it, you get in logs:

2022/02/17 11:47:35 >>> in router handler
2022/02/17 11:47:35 >>> in group handler
2022/02/17 11:47:35 >>> in handler

Which means that the group handler runs after the router handler. In my opinion we should see in logs:

2022/02/17 11:47:35 >>> in group handler
2022/02/17 11:47:35 >>> in router handler
2022/02/17 11:47:35 >>> in handler

go get -u github.com/long2ice/swagin error

Dear @long2ice,

I stumbled across your interseting article as I was searching for a manner in which to serve an openapi.yaml swagger API instance using Gin. Thank you for taking the time to write such an in-depth article.

When running the command go get -u github.com/long2ice/swagin it returns with the following error:

$ go get -u github.com/long2ice/swagin
go: downloading github.com/getkin/kin-openapi v0.80.0
# github.com/long2ice/swagin/router
/home/ben/go/pkg/mod/github.com/long2ice/[email protected]/router/router.go:31:14: c.ShouldBindRequest undefined (type *gin.Context has no field or method ShouldBindRequest)

When following the error, it leads me to the below function in router/router.go:

func BindModel(api IAPI) gin.HandlerFunc {
	return func(c *gin.Context) {
		if err := c.ShouldBindRequest(api); err != nil {
			c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			return
		}
		c.Next()
	}
}

which is strange because when reading about gin.Context it takes you here:

// Context is the most important part of gin. It allows us to pass variables between middleware,
// manage the flow, validate the JSON of a request and render a JSON response for example.
type Context struct {
...
)

Prior to having tried this I followed your guide and had quite a few errors. I then used the code that you had in: swagin/examples/ instead and got the exact same mistake as is already outlined here above.

If you could please be so kind as to help me with this it'll be greatly appreciated :)

Furthermore, the reason I'm wanting to make use of your repository - and as mentioned earlier - is because I want to serve my already generated openapi.yaml file using your repo. Is it possible to read a static yaml specification as opposed to a json specification? If so, how exactly would one go about this?

Lastly, I'm not wanting to serve the openapi spec with the redoc method but rather the swagger method and as per this link: https://swagin.long2ice.io/docs

Thanks in advance for your appreciated assistance.

Kind regards,

Crash when response struct contains a pointer

`panic: reflect: call of reflect.Value.Field on zero Value

goroutine 51 [running]:
reflect.Value.Field({0x0?, 0x0?, 0x0?}, 0x0?)
/usr/local/go/src/reflect/value.go:1220 +0xef
github.com/long2ice/swagin/swagger.(*Swagger).getResponseSchemaByModel(0xc0009bb8b0?, {0xe5a400?, 0x0})
/home/yt/go/pkg/mod/github.com/long2ice/[email protected]/swagger/swagger.go:190 +0x55a
github.com/long2ice/swagin/swagger.(*Swagger).getSchemaByType(0xe5a400?, {0xe5a400?, 0x0}, 0x0)
/home/yt/go/pkg/mod/github.com/long2ice/[email protected]/swagger/swagger.go:110 +0x66b
github.com/long2ice/swagin/swagger.(*Swagger).getResponseSchemaByModel(0xf8a2a0?, {0x1053fc0?, 0xc00061f560})
/home/yt/go/pkg/mod/github.com/long2ice/[email protected]/swagger/swagger.go:191 +0x57a
github.com/long2ice/swagin/swagger.(*Swagger).getResponses(0xc0009bbd18?, 0xc0009abd70?, {0x0, 0x0})
/home/yt/go/pkg/mod/github.com/long2ice/[email protected]/swagger/swagger.go:228 +0x20a
github.com/long2ice/swagin/swagger.(*Swagger).getPaths(0xc000178a80)
/home/yt/go/pkg/mod/github.com/long2ice/[email protected]/swagger/swagger.go:333 +0x20d
github.com/long2ice/swagin/swagger.(*Swagger).BuildOpenAPI(0xc000178a80)
/home/yt/go/pkg/mod/github.com/long2ice/[email protected]/swagger/swagger.go:380 +0x214
github.com/long2ice/swagin.(*SwaGin).init(0xc00009edc0)
/home/yt/go/pkg/mod/github.com/long2ice/[email protected]/swagin.go:150 +0x33c
github.com/long2ice/swagin.(*SwaGin).Run(0xc00009edc0, {0xc0009bbf90, 0x1, 0x1})
/home/yt/go/pkg/mod/github.com/long2ice/[email protected]/swagin.go:196 +0x45
exit status 2
make: *** [Makefile:27: run] Error 1`

New release !

Hey everyone, wanted to see if there was any plan for a new release? It looks like the last release was in Sep 2021 and a lot has gone into the repo since then :)

Example doesn't build

When I try to build example in its own directory, I get:

$ go build
# github.com/long2ice/swagin/router
../../.go/pkg/mod/github.com/long2ice/[email protected]/router/router.go:31:14: c.ShouldBindRequest undefined (type *gin.Context has no field or method ShouldBindRequest)

Swagin code calls method ShouldBindRequest() on Gin context but it doesn't exist in Gin code on official repository. This is defined in a fork on long2ice repository.

File go.mod is as follows (generated with go mod tidy):

module test

go 1.17

require (
	github.com/getkin/kin-openapi v0.88.0
	github.com/gin-contrib/cors v1.3.1
	github.com/gin-gonic/gin v1.7.4
	github.com/long2ice/swagin v0.1.0
)

require (
	github.com/fatih/structtag v1.2.0 // indirect
	github.com/ghodss/yaml v1.0.0 // indirect
	github.com/gin-contrib/sse v0.1.0 // indirect
	github.com/go-openapi/jsonpointer v0.19.5 // indirect
	github.com/go-openapi/swag v0.19.5 // indirect
	github.com/go-playground/locales v0.13.0 // indirect
	github.com/go-playground/universal-translator v0.17.0 // indirect
	github.com/go-playground/validator/v10 v10.4.1 // indirect
	github.com/golang/protobuf v1.3.3 // indirect
	github.com/json-iterator/go v1.1.9 // indirect
	github.com/leodido/go-urn v1.2.0 // indirect
	github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e // indirect
	github.com/mattn/go-isatty v0.0.12 // indirect
	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
	github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 // indirect
	github.com/ugorji/go/codec v1.1.7 // indirect
	golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect
	golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e // indirect
	gopkg.in/yaml.v2 v2.3.0 // indirect
)

What is strange, is that it works if, in the Swagin project, I run:

$ cd examples
$ go build

Panic recovery

When a panic is raised in an handler, server doesn't catch and manage this panic to return a status 500 (Internal Server Error) response. Instead, it returns nothing to the client. This is demonstrated with this code:

package main

import (
	"github.com/gin-gonic/gin"
	"github.com/long2ice/swagin"
	"github.com/long2ice/swagin/router"
	"github.com/long2ice/swagin/swagger"
)

func main() {
	app := swagin.New(swagger.New("SwaGin", "Swagger + Gin = SwaGin", "0.1.0"))
	app.GET("/", test)
	if err := app.Run(); err != nil {
		panic(err)
	}
}

var test = router.New(
	&Test{},
	router.Summary("Test query"),
	router.Description("Test query model"),
)

type Test struct{}

func (t *Test) Handler(ctx *gin.Context) {
	panic("test")
}

This can be fixed adding a recovery handler to the Gin engine. This handler will trap the panic and return appropriate response to client.

Recommend Projects

  • React photo React

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

  • Vue.js photo Vue.js

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

  • Typescript photo Typescript

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

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

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

Recommend Topics

  • javascript

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

  • web

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

  • server

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

  • Machine learning

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

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

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

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.