1
0
mirror of https://github.com/beego/bee.git synced 2024-11-29 11:41:28 +00:00

bee support swagger2.0

This commit is contained in:
astaxie 2016-08-08 16:44:49 +08:00
parent e2cd28d5f5
commit d9f182c84f

181
g_docs.go
View File

@ -45,7 +45,7 @@ const (
var pkgCache map[string]struct{} //pkg:controller:function:comments comments: key:value var pkgCache map[string]struct{} //pkg:controller:function:comments comments: key:value
var controllerComments map[string]string var controllerComments map[string]string
var importlist map[string]string var importlist map[string]string
var controllerList map[string]map[string]swagger.Item //controllername Paths items var controllerList map[string]map[string]*swagger.Item //controllername Paths items
var modelsList map[string]map[string]swagger.Schema var modelsList map[string]map[string]swagger.Schema
var rootapi swagger.Swagger var rootapi swagger.Swagger
@ -53,7 +53,7 @@ func init() {
pkgCache = make(map[string]struct{}) pkgCache = make(map[string]struct{})
controllerComments = make(map[string]string) controllerComments = make(map[string]string)
importlist = make(map[string]string) importlist = make(map[string]string)
controllerList = make(map[string]map[string]swagger.Item) controllerList = make(map[string]map[string]*swagger.Item)
modelsList = make(map[string]map[string]swagger.Schema) modelsList = make(map[string]map[string]swagger.Schema)
} }
@ -91,6 +91,7 @@ func generateDocs(curpath string) {
} }
} }
} }
// analisys controller package
for _, im := range f.Imports { for _, im := range f.Imports {
localName := "" localName := ""
if im.Name != nil { if im.Name != nil {
@ -102,33 +103,41 @@ func generateDocs(curpath string) {
switch specDecl := d.(type) { switch specDecl := d.(type) {
case *ast.FuncDecl: case *ast.FuncDecl:
for _, l := range specDecl.Body.List { for _, l := range specDecl.Body.List {
switch smtp := l.(type) { switch stmt := l.(type) {
case *ast.AssignStmt: case *ast.AssignStmt:
for _, l := range smtp.Rhs { for _, l := range stmt.Rhs {
if v, ok := l.(*ast.CallExpr); ok { if v, ok := l.(*ast.CallExpr); ok {
f, params := analisysNewNamespace(v) // analisys NewNamespace, it will return version and the subfunction
globalDocsTemplate = strings.Replace(globalDocsTemplate, "{{.version}}", f, -1) version, params := analisysNewNamespace(v)
rootapi.BasePath = version
for _, p := range params { for _, p := range params {
switch pp := p.(type) { switch pp := p.(type) {
case *ast.CallExpr: case *ast.CallExpr:
controllerName := ""
if selname := pp.Fun.(*ast.SelectorExpr).Sel.String(); selname == "NSNamespace" { if selname := pp.Fun.(*ast.SelectorExpr).Sel.String(); selname == "NSNamespace" {
s, params := analisysNewNamespace(pp) s, params := analisysNewNamespace(pp)
subapi := swagger.APIRef{Path: s}
controllerName := ""
for _, sp := range params { for _, sp := range params {
switch pp := sp.(type) { switch pp := sp.(type) {
case *ast.CallExpr: case *ast.CallExpr:
if pp.Fun.(*ast.SelectorExpr).Sel.String() == "NSInclude" { if pp.Fun.(*ast.SelectorExpr).Sel.String() == "NSInclude" {
controllerName = analisysNSInclude(s, pp) controllerName = analisysNSInclude(s, pp)
}
}
}
if v, ok := controllerComments[controllerName]; ok { if v, ok := controllerComments[controllerName]; ok {
subapi.Description = v rootapi.Tags = append(rootapi.Tags, swagger.Tag{
Name: s,
Description: v,
})
}
}
}
} }
rootapi.APIs = append(rootapi.APIs, subapi)
} else if selname == "NSInclude" { } else if selname == "NSInclude" {
analisysNSInclude(f, pp) controllerName = analisysNSInclude("", pp)
if v, ok := controllerComments[controllerName]; ok {
rootapi.Tags = append(rootapi.Tags, swagger.Tag{
Name: controllerName, // if the NSInclude has no prefix, we use the controllername as the tag
Description: v,
})
}
} }
} }
} }
@ -152,6 +161,7 @@ func generateDocs(curpath string) {
} }
} }
// return version and the others params
func analisysNewNamespace(ce *ast.CallExpr) (first string, others []ast.Expr) { func analisysNewNamespace(ce *ast.CallExpr) (first string, others []ast.Expr) {
for i, p := range ce.Args { for i, p := range ce.Args {
if i == 0 { if i == 0 {
@ -168,32 +178,45 @@ func analisysNewNamespace(ce *ast.CallExpr) (first string, others []ast.Expr) {
func analisysNSInclude(baseurl string, ce *ast.CallExpr) string { func analisysNSInclude(baseurl string, ce *ast.CallExpr) string {
cname := "" cname := ""
a := &swagger.APIDeclaration{}
a.APIVersion = rootapi.APIVersion
a.SwaggerVersion = swagger.SwaggerVersion
a.ResourcePath = baseurl
a.Produces = []string{"application/json", "application/xml", "text/plain", "text/html"}
a.APIs = make([]swagger.API, 0)
a.Models = make(map[string]swagger.Model)
for _, p := range ce.Args { for _, p := range ce.Args {
x := p.(*ast.UnaryExpr).X.(*ast.CompositeLit).Type.(*ast.SelectorExpr) x := p.(*ast.UnaryExpr).X.(*ast.CompositeLit).Type.(*ast.SelectorExpr)
if v, ok := importlist[fmt.Sprint(x.X)]; ok { if v, ok := importlist[fmt.Sprint(x.X)]; ok {
cname = v + x.Sel.Name cname = v + x.Sel.Name
} }
if apis, ok := controllerList[cname]; ok { if apis, ok := controllerList[cname]; ok {
if len(a.APIs) > 0 { for rt, item := range apis {
a.APIs = append(a.APIs, apis...) tag := ""
if baseurl != "" {
rt = "/" + baseurl + rt
tag = baseurl
} else { } else {
a.APIs = apis tag = cname
} }
if item.Get != nil {
item.Get.Tags = []string{tag}
} }
if models, ok := modelsList[cname]; ok { if item.Post != nil {
for _, m := range models { item.Post.Tags = []string{tag}
a.Models[m.ID] = m }
if item.Put != nil {
item.Put.Tags = []string{tag}
}
if item.Patch != nil {
item.Patch.Tags = []string{tag}
}
if item.Head != nil {
item.Head.Tags = []string{tag}
}
if item.Delete != nil {
item.Delete.Tags = []string{tag}
}
if item.Options != nil {
item.Options.Tags = []string{tag}
}
rootapi.Paths[rt] = item
} }
} }
} }
apilist[baseurl] = a
return cname return cname
} }
@ -228,9 +251,8 @@ func analisyscontrollerPkg(localName, pkgpath string) {
if pkgRealpath != "" { if pkgRealpath != "" {
if _, ok := pkgCache[pkgpath]; ok { if _, ok := pkgCache[pkgpath]; ok {
return return
} else {
pkgCache[pkgpath] = struct{}{}
} }
pkgCache[pkgpath] = struct{}{}
} else { } else {
ColorLog("[ERRO] the %s pkg not exist in gopath\n", pkgpath) ColorLog("[ERRO] the %s pkg not exist in gopath\n", pkgpath)
os.Exit(1) os.Exit(1)
@ -263,6 +285,7 @@ func analisyscontrollerPkg(localName, pkgpath string) {
case *ast.StructType: case *ast.StructType:
_ = tp.Struct _ = tp.Struct
//parse controller definition comments //parse controller definition comments
if strings.TrimSpace(specDecl.Doc.Text()) != "" {
controllerComments[pkgpath+s.(*ast.TypeSpec).Name.String()] = specDecl.Doc.Text() controllerComments[pkgpath+s.(*ast.TypeSpec).Name.String()] = specDecl.Doc.Text()
} }
} }
@ -271,6 +294,7 @@ func analisyscontrollerPkg(localName, pkgpath string) {
} }
} }
} }
}
} }
func isSystemPackage(pkgpath string) bool { func isSystemPackage(pkgpath string) bool {
@ -294,7 +318,8 @@ func isSystemPackage(pkgpath string) bool {
// parse the func comments // parse the func comments
func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpath string) error { func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpath string) error {
innerapi := swagger.Item{} var routerPath string
var HTTPMethod string
opts := swagger.Operation{} opts := swagger.Operation{}
if comments != nil && comments.List != nil { if comments != nil && comments.List != nil {
for _, c := range comments.List { for _, c := range comments.List {
@ -305,20 +330,20 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat
if len(e1) < 1 { if len(e1) < 1 {
return errors.New("you should has router infomation") return errors.New("you should has router infomation")
} }
innerapi.Path = e1[0] routerPath = e1[0]
if len(e1) == 2 && e1[1] != "" { if len(e1) == 2 && e1[1] != "" {
e1 = strings.SplitN(e1[1], " ", 2) e1 = strings.SplitN(e1[1], " ", 2)
opts.HTTPMethod = strings.ToUpper(strings.Trim(e1[0], "[]")) HTTPMethod = strings.ToUpper(strings.Trim(e1[0], "[]"))
} else { } else {
opts.HTTPMethod = "GET" HTTPMethod = "GET"
} }
} else if strings.HasPrefix(t, "@Title") { } else if strings.HasPrefix(t, "@Title") {
opts.Nickname = strings.TrimSpace(t[len("@Title"):]) opts.OperationID = controllerName + "." + strings.TrimSpace(t[len("@Title"):])
} else if strings.HasPrefix(t, "@Description") { } else if strings.HasPrefix(t, "@Description") {
opts.Summary = strings.TrimSpace(t[len("@Description"):]) opts.Summary = strings.TrimSpace(t[len("@Description"):])
} else if strings.HasPrefix(t, "@Success") { } else if strings.HasPrefix(t, "@Success") {
ss := strings.TrimSpace(t[len("@Success"):]) ss := strings.TrimSpace(t[len("@Success"):])
rs := swagger.ResponseMessage{} rs := swagger.Response{}
st := make([]string, 3) st := make([]string, 3)
j := 0 j := 0
var tmp []rune var tmp []rune
@ -350,7 +375,7 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat
if len(tmp) > 0 && st[2] == "" { if len(tmp) > 0 && st[2] == "" {
st[2] = strings.TrimSpace(string(tmp)) st[2] = strings.TrimSpace(string(tmp))
} }
rs.Message = st[2] rs.Description = st[2]
if st[1] == "{object}" { if st[1] == "{object}" {
if st[2] == "" { if st[2] == "" {
panic(controllerName + " " + funcName + " has no object") panic(controllerName + " " + funcName + " has no object")
@ -358,16 +383,14 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat
cmpath, m, mod, realTypes := getModel(st[2]) cmpath, m, mod, realTypes := getModel(st[2])
//ll := strings.Split(st[2], ".") //ll := strings.Split(st[2], ".")
//opts.Type = ll[len(ll)-1] //opts.Type = ll[len(ll)-1]
rs.ResponseModel = m rs.Ref = "#/definitions/" + m
if _, ok := modelsList[pkgpath+controllerName]; !ok { if _, ok := modelsList[pkgpath+controllerName]; !ok {
modelsList[pkgpath+controllerName] = make(map[string]swagger.Model, 0) modelsList[pkgpath+controllerName] = make(map[string]swagger.Schema, 0)
} }
modelsList[pkgpath+controllerName][st[2]] = mod modelsList[pkgpath+controllerName][st[2]] = mod
appendModels(cmpath, pkgpath, controllerName, realTypes) appendModels(cmpath, pkgpath, controllerName, realTypes)
} }
opts.Responses[st[0]] = rs
rs.Code, _ = strconv.Atoi(st[0])
opts.ResponseMessages = append(opts.ResponseMessages, rs)
} else if strings.HasPrefix(t, "@Param") { } else if strings.HasPrefix(t, "@Param") {
para := swagger.Parameter{} para := swagger.Parameter{}
p := getparams(strings.TrimSpace(t[len("@Param "):])) p := getparams(strings.TrimSpace(t[len("@Param "):]))
@ -375,9 +398,10 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat
panic(controllerName + "_" + funcName + "'s comments @Param at least should has 4 params") panic(controllerName + "_" + funcName + "'s comments @Param at least should has 4 params")
} }
para.Name = p[0] para.Name = p[0]
para.ParamType = p[1] para.In = p[1]
pp := strings.Split(p[2], ".") pp := strings.Split(p[2], ".")
para.DataType = pp[len(pp)-1] //@TODO models.Objects need to ref
para.Type = pp[len(pp)-1]
if len(p) > 4 { if len(p) > 4 {
para.Required, _ = strconv.ParseBool(p[3]) para.Required, _ = strconv.ParseBool(p[3])
para.Description = p[4] para.Description = p[4]
@ -386,14 +410,14 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat
} }
opts.Parameters = append(opts.Parameters, para) opts.Parameters = append(opts.Parameters, para)
} else if strings.HasPrefix(t, "@Failure") { } else if strings.HasPrefix(t, "@Failure") {
rs := swagger.ResponseMessage{} rs := swagger.Response{}
st := strings.TrimSpace(t[len("@Failure"):]) st := strings.TrimSpace(t[len("@Failure"):])
var cd []rune var cd []rune
var start bool var start bool
for i, s := range st { for i, s := range st {
if unicode.IsSpace(s) { if unicode.IsSpace(s) {
if start { if start {
rs.Message = strings.TrimSpace(st[i+1:]) rs.Description = strings.TrimSpace(st[i+1:])
break break
} else { } else {
continue continue
@ -402,10 +426,9 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat
start = true start = true
cd = append(cd, s) cd = append(cd, s)
} }
rs.Code, _ = strconv.Atoi(string(cd)) opts.Responses[string(cd)] = rs
opts.ResponseMessages = append(opts.ResponseMessages, rs) } else if strings.HasPrefix(t, "@Deprecated") {
} else if strings.HasPrefix(t, "@Type") { opts.Deprecated, _ = strconv.ParseBool(strings.TrimSpace(t[len("@Deprecated"):]))
opts.Type = strings.TrimSpace(t[len("@Type"):])
} else if strings.HasPrefix(t, "@Accept") { } else if strings.HasPrefix(t, "@Accept") {
accepts := strings.Split(strings.TrimSpace(strings.TrimSpace(t[len("@Accept"):])), ",") accepts := strings.Split(strings.TrimSpace(strings.TrimSpace(t[len("@Accept"):])), ",")
for _, a := range accepts { for _, a := range accepts {
@ -427,14 +450,35 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat
} }
} }
} }
innerapi.Operations = append(innerapi.Operations, opts) if routerPath != "" {
if innerapi.Path != "" { var item *swagger.Item
if _, ok := controllerList[pkgpath+controllerName]; ok { if itemList, ok := controllerList[pkgpath+controllerName]; ok {
controllerList[pkgpath+controllerName] = append(controllerList[pkgpath+controllerName], innerapi) if it, ok := itemList[routerPath]; !ok {
item = &swagger.Item{}
} else { } else {
controllerList[pkgpath+controllerName] = make([]swagger.API, 1) item = it
controllerList[pkgpath+controllerName][0] = innerapi
} }
} else {
controllerList[pkgpath+controllerName] = make(map[string]*swagger.Item)
item = &swagger.Item{}
}
switch HTTPMethod {
case "GET":
item.Get = &opts
case "POST":
item.Post = &opts
case "PUT":
item.Put = &opts
case "PATCH":
item.Patch = &opts
case "DELETE":
item.Delete = &opts
case "Head":
item.Head = &opts
case "OPTIONS":
item.Options = &opts
}
controllerList[pkgpath+controllerName][routerPath] = item
} }
return nil return nil
} }
@ -470,7 +514,7 @@ func getparams(str string) []string {
return r return r
} }
func getModel(str string) (pkgpath, objectname string, m swagger.Model, realTypes []string) { func getModel(str string) (pkgpath, objectname string, m swagger.Schema, realTypes []string) {
strs := strings.Split(str, ".") strs := strings.Split(str, ".")
objectname = strs[len(strs)-1] objectname = strs[len(strs)-1]
pkgpath = strings.Join(strs[:len(strs)-1], "/") pkgpath = strings.Join(strs[:len(strs)-1], "/")
@ -503,21 +547,25 @@ func getModel(str string) (pkgpath, objectname string, m swagger.Model, realType
if !ok { if !ok {
continue continue
} }
m.ID = k m.Title = k
if st.Fields.List != nil { if st.Fields.List != nil {
m.Properties = make(map[string]swagger.ModelProperty) m.Properties = make(map[string]swagger.Propertie)
for _, field := range st.Fields.List { for _, field := range st.Fields.List {
isSlice, realType := typeAnalyser(field) isSlice, realType := typeAnalyser(field)
realTypes = append(realTypes, realType) realTypes = append(realTypes, realType)
mp := swagger.ModelProperty{} mp := swagger.Propertie{}
// add type slice // add type slice
if isSlice { if isSlice {
if isBasicType(realType) {
mp.Type = "[]" + realType
} else {
mp.Type = "array" mp.Type = "array"
mp.Items = make(map[string]string) mp.Properties = make(map[string]swagger.Propertie)
mp.Items["$ref"] = realType if isBasicType(realType) {
mp.Properties["items"] = swagger.Propertie{
Type: realType,
}
} else {
mp.Properties["items"] = swagger.Propertie{
Ref: "#/definitions/" + realType,
}
} }
} else { } else {
mp.Type = realType mp.Type = realType
@ -577,10 +625,11 @@ func getModel(str string) (pkgpath, objectname string, m swagger.Model, realType
} }
} }
} }
if m.ID == "" { if m.Title == "" {
ColorLog("can't find the object: %v", str) ColorLog("can't find the object: %v", str)
os.Exit(1) os.Exit(1)
} }
rootapi.Definitions[objectname] = m
return return
} }