One thing I really like about go-restful is their built-in Swagger integration. Check out the example project: https://github.com/emicklei/mora#swagger
Some things I really love about Gin is the focus on performance, and overall, it just seems like a much better designed framework. However, I think that auto-generated documentation with Swagger is pretty awesome. It makes it easy for new engineers to get up-to-speed quickly by reading through the docs, and I especially love the way you can auto-generate API clients for lots of different languages.
I'm not sure how involved this change would be, I'm not sure if this could be done as an extension. But we could probably take lots of inspiration from (or just copy) the swagger code from go-restful (MIT licensed).
Anyway, this is not a huge priority for me, just a nice to have.
Comment From: rogeriomarques
+1
Comment From: phalt
+1
Comment From: redstrike
+1, swagger is very useful for API development
Comment From: matejkramny
:+1:
Comment From: javierprovecho
@phalt @redstrike @matejkramny @rogeriomarques @ndbroadbent I'll add support for it with v0.6 in mind. Thank you for submitting this request.
Comment From: salimane
+1
Comment From: colthreepv
I'm gonna raise the biddings on this issue, Ok for Swagger, but why not RAML? In my personal experience using both swagger and raml barebone, the latter has a better explained specification and has a more pragmatic approach.
I'll explore what can be done with my limited golang skills, and keep you posted
Comment From: colthreepv
One of the main concerns in thinking this feature out, is design this swagger/raml feature producing a base json/raml document that can be extended to let the user exploit the full-feature set of their API specifications.
I hardly seen that in other frameworks, but I'm hoping someone could propose some input regarding this
Comment From: njuettner
+1 for swagger support
Comment From: itsjamie
I'm surprised there isn't someone who has created a parser that looks at an AST + attached routes for net/http to generate Swagger/RAML style documentation.
If it isn't a project, perhaps that would be the better method of implementation for this? Rather than having it part of Gin core, if it was a separate binary that scanned a package?
Comment From: colthreepv
I think to externalize API documentation, gin.router should be exported (so Router). Moreso, I think model validation should be expanded before implementing API documentation, since there is a big difference between the vastity of API validation options and gin ones.
Comment From: DustinHigginbotham
Noticed Swagger support was on the README checklist for V1. That seems to be gone now. Should we not have our hopes up for this being released?
Comment From: manucorporat
@dustinwtf We do not need swagger support in 1.0. 1.0 is about having a stable API, production-ready and performance.
Once 1.0 final is reached, we can focus in features. Also, I do not think the swagger support should be part of Gin core. Probably a different package.
Comment From: DustinHigginbotham
Agreed! That's definitely a good focus. Just wondered considering it was there and disappeared.
Comment From: nazwa
Is there an easy way to get all the registered routes now? Like a map of url -> handlers or something.
Comment From: manucorporat
@nazwa no, we need to implement it. Two solutions: 1. Iterate over the trees and collect the routes. 2. Save the routes in a separated slice (only for documentation purposes)
While the second way is the easiest to implement it adds a unneeded overhead and complexity all over the place. So I would try to iterate the trees, it can not be that hard.
*Engine
should have a method called Routes() []RouteInfo or something like that. We do not want to expose the trees directly.
Comment From: manucorporat
Also, since the router is not longer dependent of the HttpRouter package, we have access to all the info.
Comment From: manucorporat
something like this, I do not have time to do it just now. If anybody is interested, please create a PR!!
func (engine *Engine) Routes() (routes []RouteInfo) {
for _, root := range engine.trees {
routes = iterate(routes, root)
}
return routes
}
func iterate(routes []RouteInfo, root *node) []RouteInfo {
for _, node := range root.children {
routes = iterate(routes, node)
}
if root.handlers != nil {
routes = append(routes,root.path)
}
return routes
}
Comment From: nazwa
Yea, something like this would be perfect. I'll try to have a proper play with it in the evening, as it definitely would be useful in the long run.
Comment From: manucorporat
@nazwa @dustinwtf @ndbroadbent https://github.com/gin-gonic/gin/commit/45dd7776938fddc914e8a2fe60367a3eb99e1e53
Comment From: manucorporat
router := gin.New()
router.GET("/favicon.ico", handler)
router.GET("/", handler)
group := router.Group("/users")
group.GET("/", handler)
group.GET("/:id", handler)
group.POST("/:id", handler)
router.Static("/static", ".")
fmt.Println(router.Routes())
[{GET /} {GET /users/} {GET /users/:id} {GET /favicon.ico} {GET /static/*filepath} {POST /users/:id} {HEAD /static/*filepath}]
Comment From: manucorporat
Here's a proposal for auto API documentation:
- engine.Routes()
returns a list of the RouteInfo(method, path, handlerName)
{"GET", "/users", "main.usersHandler"}
- beego special comments:
go
// @Description get all users
// @Success 200 {object}
func usersHandler(c *gin.Context) {
// ..
}
The swagger generator will use the information from the comments + engine.Routes() to generate the documentation.
Comment From: DustinHigginbotham
This is perfect, @manucorporat! When do you see this being merged into master?
Comment From: manucorporat
@dustinwtf done! https://github.com/gin-gonic/gin/commit/451f3b988b647f9565ce2c277ab7775e3b65e363
Comment From: zserge
@manucorporat Looks great, thanks!
Is there any reason to return the handler function name as a string, not as a Func
pointer?
If I understand it correctly - the final goal is to find the comments before the functions and parse them to generate Swagger/RAML docs? Probably the file name and line number would be more helpful (like returned by the Func.FileLine
). Then the parser could just read the lines above it.
Comment From: manucorporat
@zserge A pointer to a function is too low level, in my opinion, a high level API (like Gin) should hide low level details, it makes everybody's code safer. For example: if the middleware API would allow to jump between handlers and do crazy things, the whole community of middleware would be more error-prone and unsecure.
I like the idea of providing the filename and line, that's why I created the RouteInfo
struct, we can add new fields in a completely backcompatible way! what about FileName string
and FileLine uint
?
https://github.com/gin-gonic/gin/blob/master/gin.go#L37-L41
Comment From: MattDavisRV
Is there a working example of how to use this with one of the swagger generators?
Comment From: lluvio
@MattDavisRV +1
Comment From: otraore
+1
Comment From: daemonza
+1 on a example of how to use this
Comment From: verdverm
Ok, here is an example, but it requires modifying the Gin codes slightly
Outline
- Step 1. go get the swagger tool
- Step 2. build swagger-ui
- Step 3. modify `github.com/gin-gonic/gin/gin.go
- Step 4. add a
swagger.go
file at the root of your project - Step 5. pass your Router to be swaggified
- Step 6. Annotate your code with comments (see the swagger tool docs)
- Step 7. run the swagger tool
- Step 8. run your API!!!
I may have missed something, let me know and I will edit this.
You may only have to add the HandleHTTP field to Engine. I wanted the same JSON responses on OPTIONS requests.
Cheers!
~ Tony
Steps in detail
Step 1. go get the swagger tool
go get github.com/yvasiyarov/swagger
Step 2. build swagger-ui
git clone https://github.com/swagger-api/swagger-ui
cd swagger-ui
npm install
gulp
If you make any modifications to swagger-ui, be sure to rerun gulp
Step 3. modify github.com/gin-gonic/gin/gin.go
Add the following fields to the Engine definition [~ line 32]
HandleOPTIONS bool
ApiDescriptionsJson map[string]string
Add fields to the engine.New() call [~ line 107 now]
HandleOPTIONS: true,`
The remainder of step 3 may be unnecessary, however you will be a better OPTIONS netizen if you do.
Change handleHTTPRequest to the following [~ line 274 now]
func (engine *Engine) handleHTTPRequest(context *Context) {
httpMethod := context.Request.Method
path := context.Request.URL.Path
// Find root of the tree for the given HTTP method
t := engine.trees
for i, tl := 0, len(t); i < tl; i++ {
if t[i].method == httpMethod {
root := t[i].root
// Find route in tree
handlers, params, tsr := root.getValue(path, context.Params)
if handlers != nil {
context.handlers = handlers
context.Params = params
context.Next()
context.writermem.WriteHeaderNow()
return
} else if httpMethod != "CONNECT" && path != "/" {
if tsr && engine.RedirectTrailingSlash {
redirectTrailingSlash(context)
return
}
if engine.RedirectFixedPath && redirectFixedPath(context, root, engine.RedirectFixedPath) {
return
}
}
break
}
}
if httpMethod == "OPTIONS" {
// Handle OPTIONS requests
if engine.HandleOPTIONS {
if allow := engine.allowed(path, httpMethod, context.Params); len(allow) > 0 {
apiKey := strings.Trim(context.Request.RequestURI, "/")
if json, ok := engine.ApiDescriptionsJson[apiKey]; ok {
t, e := template.New("desc").Parse(json)
if e != nil {
serveError(context, http.StatusInternalServerError, []byte("Internal Server Error 500\n"))
return
}
t.Execute(context.Writer, context.Request.Host)
context.writermem.Header()["Allow"] = []string{allow}
}
context.writermem.WriteHeaderNow()
return
}
}
} else {
// TODO: unit test
if engine.HandleMethodNotAllowed {
for _, tree := range engine.trees {
if tree.method != httpMethod {
if handlers, _, _ := tree.root.getValue(path, nil); handlers != nil {
context.handlers = engine.allNoMethod
serveError(context, 405, default405Body)
return
}
}
}
}
}
context.handlers = engine.allNoRoute
serveError(context, 404, default404Body)
}
Add the following just above the handleHTTPRequest
func (engine *Engine) allowed(path, reqMethod string, po Params) (allow string) {
if path == "*" { // server-wide
for _, T := range engine.trees {
if T.method == "OPTIONS" {
continue
}
// add request method to list of allowed methods
if len(allow) == 0 {
allow = T.method
} else {
allow += ", " + T.method
}
}
} else { // specific path
for _, T := range engine.trees {
// Skip the requested method - we already tried this one
if T.method == reqMethod || T.method == "OPTIONS" {
continue
}
handle, _, _ := engine.trees.get(T.method).getValue(path, po)
if handle != nil {
// add request method to list of allowed methods
if len(allow) == 0 {
allow = T.method
} else {
allow += ", " + T.method
}
}
}
}
if len(allow) > 0 {
allow += ", OPTIONS"
}
return
}
Step 4. add a swagger.go
file at the root of your project
Be sure to update SWAGDIR
to the location of the swagger-ui
repository
package main
import (
"flag"
"net/http"
"strings"
"text/template"
"github.com/gin-gonic/gin"
)
var SWAGDIR = "../swagger-ui/dist"
var staticContent = flag.String("staticPath", SWAGDIR, "Path to folder with Swagger UI")
var apiurl = flag.String("api", "http://localhost:8080", "The base path URI of the API service")
func swaggify(router *gin.Engine) {
// Swagger Routes
router.GET("/", IndexHandler)
router.Static("/swagger-ui", *staticContent)
for apiKey := range apiDescriptionsJson {
router.GET("/"+apiKey+"/", ApiDescriptionHandler)
}
// API json data
router.ApiDescriptionsJson = apiDescriptionsJson
}
func IndexHandler(c *gin.Context) {
w := c.Writer
r := c.Request
isJsonRequest := false
if acceptHeaders, ok := r.Header["Accept"]; ok {
for _, acceptHeader := range acceptHeaders {
if strings.Contains(acceptHeader, "json") {
isJsonRequest = true
break
}
}
}
if isJsonRequest {
t, e := template.New("desc").Parse(resourceListingJson)
if e != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
t.Execute(w, *apiurl)
} else {
http.Redirect(w, r, "/swagger-ui/", http.StatusFound)
}
}
func ApiDescriptionHandler(c *gin.Context) {
w := c.Writer
r := c.Request
apiKey := strings.Trim(r.RequestURI, "/")
if json, ok := apiDescriptionsJson[apiKey]; ok {
t, e := template.New("desc").Parse(json)
if e != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
t.Execute(w, *apiurl)
} else {
w.WriteHeader(http.StatusNotFound)
}
}
Step 5. pass your Router to be swaggified
by calling
swaggify(router)
Step 6. Annotate your code with comments (see the swagger tool docs)
add the following to your main.go
// @APIVersion 0.0.0
// @APITitle title
// @APIDescription description
// @Contact user@domain.com
// @TermsOfServiceUrl http://...
// @License MIT
// @LicenseUrl http://osensource.org/licenses/MIT
Step 7. run the swagger tool
swagger -apiPackage="path/to/your/api" -ignore "$vendor"
note path/to/your/api
is a `go getable path
Step 8. run your API!!!
Comment From: marioplumbarius
Any progress with this feature?
Comment From: landru29
I'm beginner in go; I began to rewrite a comment parser to generate swagger.json. The project is still on-going.
I decided to write my own parser as I could not manage to use https://github.com/yvasiyarov/swagger (missing deps with context
). But I use the same wording.
Some data are not in comments, such host
, scheme
and basepath
, as it depends on the environment where the API is deployed.
My project is only based on comments and does not parse the code. So it can be used with other frameworks https://github.com/landru29/swaggo
Some features are still missing. For today, only basics work.
Comment From: dz0ny
@landru29 you need to run yvasiyarov/swagger with -ignore
Comment From: landru29
- Did not work.
- Need to pass some variable in the CLI (like 'host', 'basepath', ...)
- Want a tools not linked to a framework (no code parsing, only comments)
- Want cascading sub-routes (to follow the cascading of Groups)
Comment From: jsloyer
+1
Comment From: masato25
++1
Comment From: cch123
+1
Comment From: tboerger
Please use the reactions or the subscribe button.
Comment From: harshadptl
👍
Comment From: samtech09
+1
Comment From: savaki
I've been using swagger with go using code to generate the swagger file. The project is
and I've included an example using gin, https://github.com/savaki/swag/blob/master/examples/gin/main.go
Feedback would be greatly appreciated.
Comment From: mattfarina
@savaki There's no license in github.com/savaki/swag so I can't use it. Any chance of it getting an open source license?
Comment From: alwqx
+1
Comment From: NirmalVatsyayan
+1
Comment From: jmp0xf
+65535
Comment From: MTRNord
Just wondering will there be a PR to this repo about it? I would love to use it a official way :(
Comment From: puma007
@javierprovecho when will gin official support swagger?Thanks!
Comment From: tonivj5
+1
Comment From: michelia
+1 👍
Comment From: savaki
Closing out an old question. github.com/savaki/swag is licensed Apache 2.0
Comment From: lpxxn
+1
Comment From: anasanzari
+1
Comment From: miketonks
I'm interested in this idea, and I made a middleware using @savaki 's swag library, that adds a validation middleware. This ensures that api calls have valid params based on the defined spec.
https://github.com/miketonks/swag-validator
Feedback welcome.
Comment From: chinajuanbob
+1
Comment From: infiquanta
+1
Comment From: rhzs
+1
Comment From: Pokerkoffer
any updates on this? Would love to see the swagger api description page within gin
Comment From: kostiamol
+1
Comment From: ukyiwin
+1
Comment From: schrej
Can everyone please stop spamming +1
all the time? Just react on the issue and use the "Subscribe" button on the right. I don't want to klick on the notification to see the newest comments and then just read +1
every time. GitHub added those features for a reason. Use them.
Comment From: kostiamol
@schrej You are right, but I guess we did that in order to utterly highlight the importance of the feature :wink:
Comment From: thinkerou
personally, I am using https://github.com/swaggo/gin-swagger thanks @easonlin404
Comment From: frg
@thinkerou You'll encounter a lot of limitations when you get to base models, custom types etc. If it's a small project gin-swagger is fine though.
Comment From: thinkerou
@frg thank you for reminding, I also use it for personal small project right now.
Comment From: stoicskyline
Has anyone found a favorite approach to this? Whether it is @verdverm 's custom solution or using a package like https://github.com/savaki/swag
Comment From: douyacun
hello world!
Comment From: tunglv-1933
Hi everyone, Has this done?
Comment From: nikzanda
+1
Comment From: miketonks
https://github.com/miketonks/swag-validator
Comment From: yjbdsky
+1
Comment From: w4-hanggi
I really love the concept of Fast-API which can auto generate swagger. But I don't like building API server with Python, yet.
https://fastapi.tiangolo.com/
Comment From: alswl
I'm using https://github.com/caicloud/nirvana to generate swagger automatic.
But nirvana is not continued.
Comment From: LinuxSuRen
I'm wondering if it's possible to generate the e2e testing automatically.
Comment From: Will-Mann-16
I've developed an implicit OpenAPI generator called astra that will take in your gin configuration and generate an OpenAPI 3.0 specification. Hopefully this helps solve the issue above.
Comment From: danielgtaylor
I'll point out that my framework https://github.com/danielgtaylor/huma is loosely based on FastAPI and able to provide OpenAPI & JSON Schema on top of Gin (and other popular routers). It provides a bunch of useful features and there's a simple 5 minute tutorial to get you started.
Here's the tutorial code converted to use Gin and runnable in the Go playground: https://go.dev/play/p/KVF-QCAuvDd?v=gotip
Hopefully this helps somebody.
Comment From: ctfrancia
this was opened 10 years ago. I know that there have been 3rd party solutions, but, personally I would prefer a gin solution to prevent any breaking in the future
Comment From: satokenta940
Swagger is indeed a solid choice, but I'm currently using Apidog for automatic document generation. One of the aspects that really attracts me to Apidog is the aesthetic appeal of its UI.
Comment From: wangxin688
bulit-in support for generate openapi.json/yaml is definity great feature.