mirror of
https://github.com/beego/bee.git
synced 2024-11-22 10:10:53 +00:00
3199a019d7
This patch support default value after 'type' column if total columns is six. This enhancement also is compatiable with old five columns.
942 lines
26 KiB
Go
942 lines
26 KiB
Go
// Copyright 2013 bee authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
|
// not use this file except in compliance with the License. You may obtain
|
|
// a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
// License for the specific language governing permissions and limitations
|
|
// under the License.
|
|
|
|
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"go/ast"
|
|
"go/parser"
|
|
"go/token"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"reflect"
|
|
"regexp"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"unicode"
|
|
|
|
"gopkg.in/yaml.v2"
|
|
|
|
"io/ioutil"
|
|
|
|
"github.com/astaxie/beego/swagger"
|
|
"github.com/astaxie/beego/utils"
|
|
)
|
|
|
|
const (
|
|
ajson = "application/json"
|
|
axml = "application/xml"
|
|
aplain = "text/plain"
|
|
ahtml = "text/html"
|
|
)
|
|
|
|
var pkgCache map[string]struct{} //pkg:controller:function:comments comments: key:value
|
|
var controllerComments map[string]string
|
|
var importlist map[string]string
|
|
var controllerList map[string]map[string]*swagger.Item //controllername Paths items
|
|
var modelsList map[string]map[string]swagger.Schema
|
|
var rootapi swagger.Swagger
|
|
var astPkgs map[string]*ast.Package
|
|
|
|
// refer to builtin.go
|
|
var basicTypes = map[string]string{
|
|
"bool": "boolean:",
|
|
"uint": "integer:int32",
|
|
"uint8": "integer:int32",
|
|
"uint16": "integer:int32",
|
|
"uint32": "integer:int32",
|
|
"uint64": "integer:int64",
|
|
"int": "integer:int64",
|
|
"int8": "integer:int32",
|
|
"int16:int32": "integer:int32",
|
|
"int32": "integer:int32",
|
|
"int64": "integer:int64",
|
|
"uintptr": "integer:int64",
|
|
"float32": "number:float",
|
|
"float64": "number:double",
|
|
"string": "string:",
|
|
"complex64": "number:float",
|
|
"complex128": "number:double",
|
|
"byte": "string:byte",
|
|
"rune": "string:byte",
|
|
}
|
|
|
|
func init() {
|
|
pkgCache = make(map[string]struct{})
|
|
controllerComments = make(map[string]string)
|
|
importlist = make(map[string]string)
|
|
controllerList = make(map[string]map[string]*swagger.Item)
|
|
modelsList = make(map[string]map[string]swagger.Schema)
|
|
curPath, _ := os.Getwd()
|
|
astPkgs = map[string]*ast.Package{}
|
|
parsePackagesFromDir(curPath)
|
|
}
|
|
|
|
func parsePackagesFromDir(path string) {
|
|
parsePackageFromDir(path)
|
|
list, err := ioutil.ReadDir(path)
|
|
if err != nil {
|
|
ColorLog("[ERRO] Can't read directory %s : %s\n", path, err)
|
|
os.Exit(1)
|
|
}
|
|
for _, item := range list {
|
|
if item.IsDir() && item.Name() != "vendor" {
|
|
parsePackagesFromDir(path + "/" + item.Name())
|
|
}
|
|
}
|
|
}
|
|
|
|
func parsePackageFromDir(path string) {
|
|
fileSet := token.NewFileSet()
|
|
folderPkgs, err := parser.ParseDir(fileSet, path, func(info os.FileInfo) bool {
|
|
name := info.Name()
|
|
return !info.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go")
|
|
}, parser.ParseComments)
|
|
|
|
if err != nil {
|
|
ColorLog("[ERRO] the model %s parser.ParseDir error\n", path)
|
|
os.Exit(1)
|
|
}
|
|
for k, v := range folderPkgs {
|
|
astPkgs[k] = v
|
|
}
|
|
}
|
|
|
|
func generateDocs(curpath string) {
|
|
fset := token.NewFileSet()
|
|
|
|
f, err := parser.ParseFile(fset, path.Join(curpath, "routers", "router.go"), nil, parser.ParseComments)
|
|
|
|
if err != nil {
|
|
ColorLog("[ERRO] parse router.go error\n")
|
|
os.Exit(2)
|
|
}
|
|
|
|
rootapi.Infos = swagger.Information{}
|
|
rootapi.SwaggerVersion = "2.0"
|
|
//analysis API comments
|
|
if f.Comments != nil {
|
|
for _, c := range f.Comments {
|
|
for _, s := range strings.Split(c.Text(), "\n") {
|
|
if strings.HasPrefix(s, "@APIVersion") {
|
|
rootapi.Infos.Version = strings.TrimSpace(s[len("@APIVersion"):])
|
|
} else if strings.HasPrefix(s, "@Title") {
|
|
rootapi.Infos.Title = strings.TrimSpace(s[len("@Title"):])
|
|
} else if strings.HasPrefix(s, "@Description") {
|
|
rootapi.Infos.Description = strings.TrimSpace(s[len("@Description"):])
|
|
} else if strings.HasPrefix(s, "@TermsOfServiceUrl") {
|
|
rootapi.Infos.TermsOfService = strings.TrimSpace(s[len("@TermsOfServiceUrl"):])
|
|
} else if strings.HasPrefix(s, "@Contact") {
|
|
rootapi.Infos.Contact.EMail = strings.TrimSpace(s[len("@Contact"):])
|
|
} else if strings.HasPrefix(s, "@Name") {
|
|
rootapi.Infos.Contact.Name = strings.TrimSpace(s[len("@Name"):])
|
|
} else if strings.HasPrefix(s, "@URL") {
|
|
rootapi.Infos.Contact.URL = strings.TrimSpace(s[len("@URL"):])
|
|
} else if strings.HasPrefix(s, "@License") {
|
|
if rootapi.Infos.License == nil {
|
|
rootapi.Infos.License = &swagger.License{Name: strings.TrimSpace(s[len("@License"):])}
|
|
} else {
|
|
rootapi.Infos.License.Name = strings.TrimSpace(s[len("@License"):])
|
|
}
|
|
} else if strings.HasPrefix(s, "@LicenseUrl") {
|
|
if rootapi.Infos.License == nil {
|
|
rootapi.Infos.License = &swagger.License{URL: strings.TrimSpace(s[len("@LicenseUrl"):])}
|
|
} else {
|
|
rootapi.Infos.License.URL = strings.TrimSpace(s[len("@LicenseUrl"):])
|
|
}
|
|
} else if strings.HasPrefix(s, "@Schemes") {
|
|
rootapi.Schemes = strings.Split(strings.TrimSpace(s[len("@Schemes"):]), ",")
|
|
} else if strings.HasPrefix(s, "@Host") {
|
|
rootapi.Host = strings.TrimSpace(s[len("@Host"):])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// analisys controller package
|
|
for _, im := range f.Imports {
|
|
localName := ""
|
|
if im.Name != nil {
|
|
localName = im.Name.Name
|
|
}
|
|
analisyscontrollerPkg(localName, im.Path.Value)
|
|
}
|
|
for _, d := range f.Decls {
|
|
switch specDecl := d.(type) {
|
|
case *ast.FuncDecl:
|
|
for _, l := range specDecl.Body.List {
|
|
switch stmt := l.(type) {
|
|
case *ast.AssignStmt:
|
|
for _, l := range stmt.Rhs {
|
|
if v, ok := l.(*ast.CallExpr); ok {
|
|
// analisys NewNamespace, it will return version and the subfunction
|
|
if selName := v.Fun.(*ast.SelectorExpr).Sel.String(); selName != "NewNamespace" {
|
|
continue
|
|
}
|
|
version, params := analisysNewNamespace(v)
|
|
if rootapi.BasePath == "" && version != "" {
|
|
rootapi.BasePath = version
|
|
}
|
|
for _, p := range params {
|
|
switch pp := p.(type) {
|
|
case *ast.CallExpr:
|
|
controllerName := ""
|
|
if selname := pp.Fun.(*ast.SelectorExpr).Sel.String(); selname == "NSNamespace" {
|
|
s, params := analisysNewNamespace(pp)
|
|
for _, sp := range params {
|
|
switch pp := sp.(type) {
|
|
case *ast.CallExpr:
|
|
if pp.Fun.(*ast.SelectorExpr).Sel.String() == "NSInclude" {
|
|
controllerName = analisysNSInclude(s, pp)
|
|
if v, ok := controllerComments[controllerName]; ok {
|
|
rootapi.Tags = append(rootapi.Tags, swagger.Tag{
|
|
Name: strings.Trim(s, "/"),
|
|
Description: v,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else if selname == "NSInclude" {
|
|
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,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
os.Mkdir(path.Join(curpath, "swagger"), 0755)
|
|
fd, err := os.Create(path.Join(curpath, "swagger", "swagger.json"))
|
|
fdyml, err := os.Create(path.Join(curpath, "swagger", "swagger.yml"))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer fdyml.Close()
|
|
defer fd.Close()
|
|
dt, err := json.MarshalIndent(rootapi, "", " ")
|
|
dtyml, erryml := yaml.Marshal(rootapi)
|
|
if err != nil || erryml != nil {
|
|
panic(err)
|
|
}
|
|
_, err = fd.Write(dt)
|
|
_, erryml = fdyml.Write(dtyml)
|
|
if err != nil || erryml != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
// return version and the others params
|
|
func analisysNewNamespace(ce *ast.CallExpr) (first string, others []ast.Expr) {
|
|
for i, p := range ce.Args {
|
|
if i == 0 {
|
|
switch pp := p.(type) {
|
|
case *ast.BasicLit:
|
|
first = strings.Trim(pp.Value, `"`)
|
|
}
|
|
continue
|
|
}
|
|
others = append(others, p)
|
|
}
|
|
return
|
|
}
|
|
|
|
func analisysNSInclude(baseurl string, ce *ast.CallExpr) string {
|
|
cname := ""
|
|
for _, p := range ce.Args {
|
|
x := p.(*ast.UnaryExpr).X.(*ast.CompositeLit).Type.(*ast.SelectorExpr)
|
|
if v, ok := importlist[fmt.Sprint(x.X)]; ok {
|
|
cname = v + x.Sel.Name
|
|
}
|
|
if apis, ok := controllerList[cname]; ok {
|
|
for rt, item := range apis {
|
|
tag := ""
|
|
if baseurl != "" {
|
|
rt = baseurl + rt
|
|
tag = strings.Trim(baseurl, "/")
|
|
} else {
|
|
tag = cname
|
|
}
|
|
if item.Get != nil {
|
|
item.Get.Tags = []string{tag}
|
|
}
|
|
if item.Post != nil {
|
|
item.Post.Tags = []string{tag}
|
|
}
|
|
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}
|
|
}
|
|
if len(rootapi.Paths) == 0 {
|
|
rootapi.Paths = make(map[string]*swagger.Item)
|
|
}
|
|
rt = urlReplace(rt)
|
|
rootapi.Paths[rt] = item
|
|
}
|
|
}
|
|
}
|
|
return cname
|
|
}
|
|
|
|
func analisyscontrollerPkg(localName, pkgpath string) {
|
|
pkgpath = strings.Trim(pkgpath, "\"")
|
|
if isSystemPackage(pkgpath) {
|
|
return
|
|
}
|
|
if pkgpath == "github.com/astaxie/beego" {
|
|
return
|
|
}
|
|
if localName != "" {
|
|
importlist[localName] = pkgpath
|
|
} else {
|
|
pps := strings.Split(pkgpath, "/")
|
|
importlist[pps[len(pps)-1]] = pkgpath
|
|
}
|
|
gopath := os.Getenv("GOPATH")
|
|
if gopath == "" {
|
|
panic("please set gopath")
|
|
}
|
|
pkgRealpath := ""
|
|
|
|
wgopath := filepath.SplitList(gopath)
|
|
for _, wg := range wgopath {
|
|
wg, _ = filepath.EvalSymlinks(filepath.Join(wg, "src", pkgpath))
|
|
if utils.FileExists(wg) {
|
|
pkgRealpath = wg
|
|
break
|
|
}
|
|
}
|
|
if pkgRealpath != "" {
|
|
if _, ok := pkgCache[pkgpath]; ok {
|
|
return
|
|
}
|
|
pkgCache[pkgpath] = struct{}{}
|
|
} else {
|
|
ColorLog("[ERRO] the %s pkg not exist in gopath\n", pkgpath)
|
|
os.Exit(1)
|
|
}
|
|
fileSet := token.NewFileSet()
|
|
astPkgs, err := parser.ParseDir(fileSet, pkgRealpath, func(info os.FileInfo) bool {
|
|
name := info.Name()
|
|
return !info.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go")
|
|
}, parser.ParseComments)
|
|
|
|
if err != nil {
|
|
ColorLog("[ERRO] the %s pkg parser.ParseDir error\n", pkgpath)
|
|
os.Exit(1)
|
|
}
|
|
for _, pkg := range astPkgs {
|
|
for _, fl := range pkg.Files {
|
|
for _, d := range fl.Decls {
|
|
switch specDecl := d.(type) {
|
|
case *ast.FuncDecl:
|
|
if specDecl.Recv != nil && len(specDecl.Recv.List) > 0 {
|
|
if t, ok := specDecl.Recv.List[0].Type.(*ast.StarExpr); ok {
|
|
// parse controller method
|
|
parserComments(specDecl.Doc, specDecl.Name.String(), fmt.Sprint(t.X), pkgpath)
|
|
}
|
|
}
|
|
case *ast.GenDecl:
|
|
if specDecl.Tok == token.TYPE {
|
|
for _, s := range specDecl.Specs {
|
|
switch tp := s.(*ast.TypeSpec).Type.(type) {
|
|
case *ast.StructType:
|
|
_ = tp.Struct
|
|
//parse controller definition comments
|
|
if strings.TrimSpace(specDecl.Doc.Text()) != "" {
|
|
controllerComments[pkgpath+s.(*ast.TypeSpec).Name.String()] = specDecl.Doc.Text()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func isSystemPackage(pkgpath string) bool {
|
|
goroot := runtime.GOROOT()
|
|
if goroot == "" {
|
|
panic("goroot is empty, do you install Go right?")
|
|
}
|
|
wg, _ := filepath.EvalSymlinks(filepath.Join(goroot, "src", "pkg", pkgpath))
|
|
if utils.FileExists(wg) {
|
|
return true
|
|
}
|
|
|
|
//TODO(zh):support go1.4
|
|
wg, _ = filepath.EvalSymlinks(filepath.Join(goroot, "src", pkgpath))
|
|
if utils.FileExists(wg) {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func peekNextSplitString(ss string) (s string, spacePos int) {
|
|
spacePos = strings.IndexFunc(ss, unicode.IsSpace)
|
|
if spacePos < 0 {
|
|
s = ss
|
|
spacePos = len(ss)
|
|
} else {
|
|
s = strings.TrimSpace(ss[:spacePos])
|
|
}
|
|
return
|
|
}
|
|
|
|
// parse the func comments
|
|
func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpath string) error {
|
|
var routerPath string
|
|
var HTTPMethod string
|
|
opts := swagger.Operation{
|
|
Responses: make(map[string]swagger.Response),
|
|
}
|
|
if comments != nil && comments.List != nil {
|
|
for _, c := range comments.List {
|
|
t := strings.TrimSpace(strings.TrimLeft(c.Text, "//"))
|
|
if strings.HasPrefix(t, "@router") {
|
|
elements := strings.TrimSpace(t[len("@router"):])
|
|
e1 := strings.SplitN(elements, " ", 2)
|
|
if len(e1) < 1 {
|
|
return errors.New("you should has router infomation")
|
|
}
|
|
routerPath = e1[0]
|
|
if len(e1) == 2 && e1[1] != "" {
|
|
e1 = strings.SplitN(e1[1], " ", 2)
|
|
HTTPMethod = strings.ToUpper(strings.Trim(e1[0], "[]"))
|
|
} else {
|
|
HTTPMethod = "GET"
|
|
}
|
|
} else if strings.HasPrefix(t, "@Title") {
|
|
opts.OperationID = controllerName + "." + strings.TrimSpace(t[len("@Title"):])
|
|
} else if strings.HasPrefix(t, "@Description") {
|
|
opts.Description = strings.TrimSpace(t[len("@Description"):])
|
|
} else if strings.HasPrefix(t, "@Summary") {
|
|
opts.Summary = strings.TrimSpace(t[len("@Summary"):])
|
|
} else if strings.HasPrefix(t, "@Success") {
|
|
ss := strings.TrimSpace(t[len("@Success"):])
|
|
rs := swagger.Response{}
|
|
respCode, pos := peekNextSplitString(ss)
|
|
ss = strings.TrimSpace(ss[pos:])
|
|
respType, pos := peekNextSplitString(ss)
|
|
if respType == "{object}" || respType == "{array}" {
|
|
isArray := respType == "{array}"
|
|
ss = strings.TrimSpace(ss[pos:])
|
|
schemaName, pos := peekNextSplitString(ss)
|
|
if schemaName == "" {
|
|
ColorLog("[ERRO][%s.%s] Schema must follow {object} or {array}\n", controllerName, funcName)
|
|
os.Exit(-1)
|
|
}
|
|
if strings.HasPrefix(schemaName, "[]") {
|
|
schemaName = schemaName[2:]
|
|
isArray = true
|
|
}
|
|
schema := swagger.Schema{}
|
|
if sType, ok := basicTypes[schemaName]; ok {
|
|
typeFormat := strings.Split(sType, ":")
|
|
schema.Type = typeFormat[0]
|
|
schema.Format = typeFormat[1]
|
|
} else {
|
|
m, mod, realTypes := getModel(schemaName)
|
|
schema.Ref = "#/definitions/" + m
|
|
if _, ok := modelsList[pkgpath+controllerName]; !ok {
|
|
modelsList[pkgpath+controllerName] = make(map[string]swagger.Schema, 0)
|
|
}
|
|
modelsList[pkgpath+controllerName][schemaName] = mod
|
|
appendModels(pkgpath, controllerName, realTypes)
|
|
}
|
|
if isArray {
|
|
rs.Schema = &swagger.Schema{
|
|
Type: "array",
|
|
Items: &schema,
|
|
}
|
|
} else {
|
|
rs.Schema = &schema
|
|
}
|
|
rs.Description = strings.TrimSpace(ss[pos:])
|
|
} else {
|
|
rs.Description = strings.TrimSpace(ss)
|
|
}
|
|
opts.Responses[respCode] = rs
|
|
} else if strings.HasPrefix(t, "@Param") {
|
|
para := swagger.Parameter{}
|
|
p := getparams(strings.TrimSpace(t[len("@Param "):]))
|
|
if len(p) < 4 {
|
|
panic(controllerName + "_" + funcName + "'s comments @Param at least should has 4 params")
|
|
}
|
|
para.Name = p[0]
|
|
switch p[1] {
|
|
case "query":
|
|
fallthrough
|
|
case "header":
|
|
fallthrough
|
|
case "path":
|
|
fallthrough
|
|
case "formData":
|
|
fallthrough
|
|
case "body":
|
|
break
|
|
default:
|
|
ColorLog("[WARN][%s.%s] Unknow param location: %s, Possible values are `query`, `header`, `path`, `formData` or `body`.\n", controllerName, funcName, p[1])
|
|
}
|
|
para.In = p[1]
|
|
pp := strings.Split(p[2], ".")
|
|
typ := pp[len(pp)-1]
|
|
if len(pp) >= 2 {
|
|
m, mod, realTypes := getModel(p[2])
|
|
para.Schema = &swagger.Schema{
|
|
Ref: "#/definitions/" + m,
|
|
}
|
|
if _, ok := modelsList[pkgpath+controllerName]; !ok {
|
|
modelsList[pkgpath+controllerName] = make(map[string]swagger.Schema, 0)
|
|
}
|
|
modelsList[pkgpath+controllerName][typ] = mod
|
|
appendModels(pkgpath, controllerName, realTypes)
|
|
} else {
|
|
isArray := false
|
|
paraType := ""
|
|
paraFormat := ""
|
|
if strings.HasPrefix(typ, "[]") {
|
|
typ = typ[2:]
|
|
isArray = true
|
|
}
|
|
if typ == "string" || typ == "number" || typ == "integer" || typ == "boolean" ||
|
|
typ == "array" || typ == "file" {
|
|
paraType = typ
|
|
} else if sType, ok := basicTypes[typ]; ok {
|
|
typeFormat := strings.Split(sType, ":")
|
|
paraType = typeFormat[0]
|
|
paraFormat = typeFormat[1]
|
|
} else {
|
|
ColorLog("[WARN][%s.%s] Unknow param type: %s\n", controllerName, funcName, typ)
|
|
}
|
|
if isArray {
|
|
para.Type = "array"
|
|
para.Items = &swagger.ParameterItems{
|
|
Type: paraType,
|
|
Format: paraFormat,
|
|
}
|
|
} else {
|
|
para.Type = paraType
|
|
para.Format = paraFormat
|
|
}
|
|
}
|
|
switch len(p) {
|
|
case 5:
|
|
para.Required, _ = strconv.ParseBool(p[3])
|
|
para.Description = strings.Trim(p[4], `" `)
|
|
case 6:
|
|
para.Default = str2RealType(p[3], para.Type)
|
|
para.Required, _ = strconv.ParseBool(p[4])
|
|
para.Description = strings.Trim(p[5], `" `)
|
|
default:
|
|
para.Description = strings.Trim(p[3], `" `)
|
|
}
|
|
opts.Parameters = append(opts.Parameters, para)
|
|
} else if strings.HasPrefix(t, "@Failure") {
|
|
rs := swagger.Response{}
|
|
st := strings.TrimSpace(t[len("@Failure"):])
|
|
var cd []rune
|
|
var start bool
|
|
for i, s := range st {
|
|
if unicode.IsSpace(s) {
|
|
if start {
|
|
rs.Description = strings.TrimSpace(st[i+1:])
|
|
break
|
|
} else {
|
|
continue
|
|
}
|
|
}
|
|
start = true
|
|
cd = append(cd, s)
|
|
}
|
|
opts.Responses[string(cd)] = rs
|
|
} else if strings.HasPrefix(t, "@Deprecated") {
|
|
opts.Deprecated, _ = strconv.ParseBool(strings.TrimSpace(t[len("@Deprecated"):]))
|
|
} else if strings.HasPrefix(t, "@Accept") {
|
|
accepts := strings.Split(strings.TrimSpace(strings.TrimSpace(t[len("@Accept"):])), ",")
|
|
for _, a := range accepts {
|
|
switch a {
|
|
case "json":
|
|
opts.Consumes = append(opts.Consumes, ajson)
|
|
opts.Produces = append(opts.Produces, ajson)
|
|
case "xml":
|
|
opts.Consumes = append(opts.Consumes, axml)
|
|
opts.Produces = append(opts.Produces, axml)
|
|
case "plain":
|
|
opts.Consumes = append(opts.Consumes, aplain)
|
|
opts.Produces = append(opts.Produces, aplain)
|
|
case "html":
|
|
opts.Consumes = append(opts.Consumes, ahtml)
|
|
opts.Produces = append(opts.Produces, ahtml)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if routerPath != "" {
|
|
var item *swagger.Item
|
|
if itemList, ok := controllerList[pkgpath+controllerName]; ok {
|
|
if it, ok := itemList[routerPath]; !ok {
|
|
item = &swagger.Item{}
|
|
} else {
|
|
item = it
|
|
}
|
|
} 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
|
|
}
|
|
|
|
// analisys params return []string
|
|
// @Param query form string true "The email for login"
|
|
// [query form string true "The email for login"]
|
|
func getparams(str string) []string {
|
|
var s []rune
|
|
var j int
|
|
var start bool
|
|
var r []string
|
|
var quoted int8
|
|
for _, c := range []rune(str) {
|
|
if unicode.IsSpace(c) && quoted == 0 {
|
|
if !start {
|
|
continue
|
|
} else {
|
|
start = false
|
|
j++
|
|
r = append(r, string(s))
|
|
s = make([]rune, 0)
|
|
continue
|
|
}
|
|
}
|
|
|
|
start = true
|
|
if c == '"' {
|
|
quoted ^= 1
|
|
continue
|
|
}
|
|
s = append(s, c)
|
|
}
|
|
if len(s) > 0 {
|
|
r = append(r, string(s))
|
|
}
|
|
return r
|
|
}
|
|
|
|
func getModel(str string) (objectname string, m swagger.Schema, realTypes []string) {
|
|
strs := strings.Split(str, ".")
|
|
objectname = strs[len(strs)-1]
|
|
packageName := ""
|
|
m.Type = "object"
|
|
for _, pkg := range astPkgs {
|
|
for _, fl := range pkg.Files {
|
|
for k, d := range fl.Scope.Objects {
|
|
if d.Kind == ast.Typ {
|
|
if k != objectname {
|
|
continue
|
|
}
|
|
packageName = pkg.Name
|
|
parseObject(d, k, &m, &realTypes, astPkgs, pkg.Name)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if m.Title == "" {
|
|
ColorLog("[WARN]can't find the object: %s\n", str)
|
|
// TODO remove when all type have been supported
|
|
//os.Exit(1)
|
|
}
|
|
if len(rootapi.Definitions) == 0 {
|
|
rootapi.Definitions = make(map[string]swagger.Schema)
|
|
}
|
|
objectname = packageName + "." + objectname
|
|
rootapi.Definitions[objectname] = m
|
|
return
|
|
}
|
|
|
|
func parseObject(d *ast.Object, k string, m *swagger.Schema, realTypes *[]string, astPkgs map[string]*ast.Package, packageName string) {
|
|
ts, ok := d.Decl.(*ast.TypeSpec)
|
|
if !ok {
|
|
ColorLog("Unknown type without TypeSec: %v\n", d)
|
|
os.Exit(1)
|
|
}
|
|
// TODO support other types, such as `ArrayType`, `MapType`, `InterfaceType` etc...
|
|
st, ok := ts.Type.(*ast.StructType)
|
|
if !ok {
|
|
return
|
|
}
|
|
m.Title = k
|
|
if st.Fields.List != nil {
|
|
m.Properties = make(map[string]swagger.Propertie)
|
|
for _, field := range st.Fields.List {
|
|
realType := ""
|
|
isSlice, realType, sType := typeAnalyser(field)
|
|
if (isSlice && isBasicType(realType)) || sType == "object" {
|
|
if len(strings.Split(realType, " ")) > 1 {
|
|
realType = strings.Replace(realType, " ", ".", -1)
|
|
realType = strings.Replace(realType, "&", "", -1)
|
|
realType = strings.Replace(realType, "{", "", -1)
|
|
realType = strings.Replace(realType, "}", "", -1)
|
|
} else {
|
|
realType = packageName + "." + realType
|
|
}
|
|
}
|
|
*realTypes = append(*realTypes, realType)
|
|
mp := swagger.Propertie{}
|
|
if isSlice {
|
|
mp.Type = "array"
|
|
if isBasicType(realType) {
|
|
typeFormat := strings.Split(sType, ":")
|
|
mp.Items = &swagger.Propertie{
|
|
Type: typeFormat[0],
|
|
Format: typeFormat[1],
|
|
}
|
|
} else {
|
|
mp.Items = &swagger.Propertie{
|
|
Ref: "#/definitions/" + realType,
|
|
}
|
|
}
|
|
} else {
|
|
if sType == "object" {
|
|
mp.Ref = "#/definitions/" + realType
|
|
} else if isBasicType(realType) {
|
|
typeFormat := strings.Split(sType, ":")
|
|
mp.Type = typeFormat[0]
|
|
mp.Format = typeFormat[1]
|
|
} else if realType == "map" {
|
|
typeFormat := strings.Split(sType, ":")
|
|
mp.AdditionalProperties = &swagger.Propertie{
|
|
Type: typeFormat[0],
|
|
Format: typeFormat[1],
|
|
}
|
|
}
|
|
}
|
|
if field.Names != nil {
|
|
|
|
// set property name as field name
|
|
var name = field.Names[0].Name
|
|
|
|
// if no tag skip tag processing
|
|
if field.Tag == nil {
|
|
m.Properties[name] = mp
|
|
continue
|
|
}
|
|
|
|
var tagValues []string
|
|
|
|
stag := reflect.StructTag(strings.Trim(field.Tag.Value, "`"))
|
|
|
|
defaultValue := stag.Get("doc")
|
|
if defaultValue != "" {
|
|
r, _ := regexp.Compile(`default\((.*)\)`)
|
|
if r.MatchString(defaultValue) {
|
|
res := r.FindStringSubmatch(defaultValue)
|
|
mp.Default = str2RealType(res[1], realType)
|
|
|
|
} else {
|
|
ColorLog("[WARN] Invalid default value: %s\n", defaultValue)
|
|
}
|
|
}
|
|
|
|
tag := stag.Get("json")
|
|
|
|
if tag != "" {
|
|
tagValues = strings.Split(tag, ",")
|
|
}
|
|
|
|
// dont add property if json tag first value is "-"
|
|
if len(tagValues) == 0 || tagValues[0] != "-" {
|
|
|
|
// set property name to the left most json tag value only if is not omitempty
|
|
if len(tagValues) > 0 && tagValues[0] != "omitempty" {
|
|
name = tagValues[0]
|
|
}
|
|
|
|
if thrifttag := stag.Get("thrift"); thrifttag != "" {
|
|
ts := strings.Split(thrifttag, ",")
|
|
if ts[0] != "" {
|
|
name = ts[0]
|
|
}
|
|
}
|
|
if required := stag.Get("required"); required != "" {
|
|
m.Required = append(m.Required, name)
|
|
}
|
|
if desc := stag.Get("description"); desc != "" {
|
|
mp.Description = desc
|
|
}
|
|
|
|
m.Properties[name] = mp
|
|
}
|
|
if ignore := stag.Get("ignore"); ignore != "" {
|
|
continue
|
|
}
|
|
} else {
|
|
for _, pkg := range astPkgs {
|
|
for _, fl := range pkg.Files {
|
|
for nameOfObj, obj := range fl.Scope.Objects {
|
|
if obj.Name == fmt.Sprint(field.Type) {
|
|
parseObject(obj, nameOfObj, m, realTypes, astPkgs, pkg.Name)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func typeAnalyser(f *ast.Field) (isSlice bool, realType, swaggerType string) {
|
|
if arr, ok := f.Type.(*ast.ArrayType); ok {
|
|
if isBasicType(fmt.Sprint(arr.Elt)) {
|
|
return false, fmt.Sprintf("[]%v", arr.Elt), basicTypes[fmt.Sprint(arr.Elt)]
|
|
}
|
|
if mp, ok := arr.Elt.(*ast.MapType); ok {
|
|
return false, fmt.Sprintf("map[%v][%v]", mp.Key, mp.Value), "object"
|
|
}
|
|
if star, ok := arr.Elt.(*ast.StarExpr); ok {
|
|
return true, fmt.Sprint(star.X), "object"
|
|
}
|
|
return true, fmt.Sprint(arr.Elt), "object"
|
|
}
|
|
switch t := f.Type.(type) {
|
|
case *ast.StarExpr:
|
|
return false, fmt.Sprint(t.X), "object"
|
|
case *ast.MapType:
|
|
val := fmt.Sprintf("%v", t.Value)
|
|
if isBasicType(val) {
|
|
return false, "map", basicTypes[val]
|
|
}
|
|
return false, val, "object"
|
|
}
|
|
if k, ok := basicTypes[fmt.Sprint(f.Type)]; ok {
|
|
return false, fmt.Sprint(f.Type), k
|
|
}
|
|
return false, fmt.Sprint(f.Type), "object"
|
|
}
|
|
|
|
func isBasicType(Type string) bool {
|
|
if _, ok := basicTypes[Type]; ok {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// regexp get json tag
|
|
func grepJSONTag(tag string) string {
|
|
r, _ := regexp.Compile(`json:"([^"]*)"`)
|
|
matches := r.FindAllStringSubmatch(tag, -1)
|
|
if len(matches) > 0 {
|
|
return matches[0][1]
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// append models
|
|
func appendModels(pkgpath, controllerName string, realTypes []string) {
|
|
for _, realType := range realTypes {
|
|
if realType != "" && !isBasicType(strings.TrimLeft(realType, "[]")) &&
|
|
!strings.HasPrefix(realType, "map") && !strings.HasPrefix(realType, "&") {
|
|
if _, ok := modelsList[pkgpath+controllerName][realType]; ok {
|
|
continue
|
|
}
|
|
_, mod, newRealTypes := getModel(realType)
|
|
modelsList[pkgpath+controllerName][realType] = mod
|
|
appendModels(pkgpath, controllerName, newRealTypes)
|
|
}
|
|
}
|
|
}
|
|
|
|
func urlReplace(src string) string {
|
|
pt := strings.Split(src, "/")
|
|
for i, p := range pt {
|
|
if len(p) > 0 {
|
|
if p[0] == ':' {
|
|
pt[i] = "{" + p[1:] + "}"
|
|
} else if p[0] == '?' && p[1] == ':' {
|
|
pt[i] = "{" + p[2:] + "}"
|
|
}
|
|
}
|
|
}
|
|
return strings.Join(pt, "/")
|
|
}
|
|
|
|
func str2RealType(s string, typ string) interface{} {
|
|
var err error
|
|
var ret interface{}
|
|
|
|
switch typ {
|
|
case "int", "int64", "int32", "int16", "int8":
|
|
ret, err = strconv.Atoi(s)
|
|
case "bool":
|
|
ret, err = strconv.ParseBool(s)
|
|
case "float64":
|
|
ret, err = strconv.ParseFloat(s, 64)
|
|
case "float32":
|
|
ret, err = strconv.ParseFloat(s, 32)
|
|
default:
|
|
return s
|
|
}
|
|
|
|
if err != nil {
|
|
ColorLog("[WARN] Invalid default value type(%s): %s\n", typ, s)
|
|
return s
|
|
}
|
|
|
|
return ret
|
|
}
|