feat: support multi-namespace to generate API version of swagger

This commit is contained in:
L 2024-04-08 21:30:05 +08:00 committed by Ming Deng
parent 431859968b
commit ae62cd713b
1 changed files with 88 additions and 33 deletions

View File

@ -56,12 +56,17 @@ const (
astTypeMap = "map" astTypeMap = "map"
) )
const defaultNamespacePrefix = ""
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 rootapiSingle = false
var rootapiDefault swagger.Swagger
var rootapiMap map[string]*swagger.Swagger // version, swagger
var astPkgs []*ast.Package var astPkgs []*ast.Package
var pkgLoadedCache map[string]struct{} var pkgLoadedCache map[string]struct{}
@ -106,6 +111,7 @@ func init() {
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)
rootapiMap = make(map[string]*swagger.Swagger)
astPkgs = make([]*ast.Package, 0) astPkgs = make([]*ast.Package, 0)
pkgLoadedCache = make(map[string]struct{}) pkgLoadedCache = make(map[string]struct{})
} }
@ -188,14 +194,21 @@ func GenerateDocs(curpath string) {
beeLogger.Log.Fatalf("Error while parsing router.go: %s", err) beeLogger.Log.Fatalf("Error while parsing router.go: %s", err)
} }
rootapi.Infos = swagger.Information{}
rootapi.SwaggerVersion = "2.0"
// Analyse API comments // Analyse API comments
if f.Comments != nil { if f.Comments != nil {
for _, c := range f.Comments { for _, c := range f.Comments {
var namespacePrefix string
rootapi := &swagger.Swagger{
Infos: swagger.Information{},
SwaggerVersion: "2.0",
}
for _, s := range strings.Split(c.Text(), "\n") { for _, s := range strings.Split(c.Text(), "\n") {
if strings.HasPrefix(s, "@APIVersion") { if strings.HasPrefix(s, "@NamespacePrefix") {
namespacePrefix = strings.TrimSpace(s[len("@NamespacePrefix"):])
if _, exist := rootapiMap[namespacePrefix]; !exist {
rootapiMap[namespacePrefix] = rootapi
}
} else if strings.HasPrefix(s, "@APIVersion") {
rootapi.Infos.Version = strings.TrimSpace(s[len("@APIVersion"):]) rootapi.Infos.Version = strings.TrimSpace(s[len("@APIVersion"):])
} else if strings.HasPrefix(s, "@Title") { } else if strings.HasPrefix(s, "@Title") {
rootapi.Infos.Title = strings.TrimSpace(s[len("@Title"):]) rootapi.Infos.Title = strings.TrimSpace(s[len("@Title"):])
@ -279,6 +292,12 @@ func GenerateDocs(curpath string) {
rootapi.Security = append(rootapi.Security, getSecurity(s)) rootapi.Security = append(rootapi.Security, getSecurity(s))
} }
} }
// when namespacePrefix not defined, then add default namespacePrefix
if _, exist := rootapiMap[namespacePrefix]; !exist {
rootapiSingle = true
rootapiDefault = *rootapi
}
} }
} }
// Analyse controller package // Analyse controller package
@ -289,6 +308,7 @@ func GenerateDocs(curpath string) {
} }
analyseControllerPkg(localName, im.Path.Value) analyseControllerPkg(localName, im.Path.Value)
} }
for _, d := range f.Decls { for _, d := range f.Decls {
switch specDecl := d.(type) { switch specDecl := d.(type) {
case *ast.FuncDecl: case *ast.FuncDecl:
@ -302,21 +322,43 @@ func GenerateDocs(curpath string) {
if !selOK || selExpr.Sel.Name != "NewNamespace" { if !selOK || selExpr.Sel.Name != "NewNamespace" {
continue continue
} }
version, params := analyseNewNamespace(v) version, params := analyseNewNamespace(v)
if rootapi.BasePath == "" && version != "" { var rootapi *swagger.Swagger
if rootapiSingle {
rootapi = &rootapiDefault
} else {
_rootapi, rootapiExist := rootapiMap[version]
if !rootapiExist {
rootapi = &swagger.Swagger{
Infos: swagger.Information{},
SwaggerVersion: "2.0",
}
rootapiMap[version] = rootapi
} else {
rootapi = _rootapi
}
}
if !rootapiSingle && rootapi.BasePath == "" {
rootapi.BasePath = version 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:
var controllerName string var controllerName string
if selname := pp.Fun.(*ast.SelectorExpr).Sel.String(); selname == "NSNamespace" { if selname := pp.Fun.(*ast.SelectorExpr).Sel.String(); selname == "NSNamespace" {
s, params := analyseNewNamespace(pp) s, params := analyseNewNamespace(pp)
if rootapiSingle {
s = path.Join(version, s)
}
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 = analyseNSInclude(s, pp) controllerName = analyseNSInclude(s, pp, rootapi)
if v, ok := controllerComments[controllerName]; ok { if v, ok := controllerComments[controllerName]; ok {
rootapi.Tags = append(rootapi.Tags, swagger.Tag{ rootapi.Tags = append(rootapi.Tags, swagger.Tag{
Name: strings.Trim(s, "/"), Name: strings.Trim(s, "/"),
@ -327,7 +369,7 @@ func GenerateDocs(curpath string) {
} }
} }
} else if selname == "NSInclude" { } else if selname == "NSInclude" {
controllerName = analyseNSInclude("", pp) controllerName = analyseNSInclude("", pp, rootapi)
if v, ok := controllerComments[controllerName]; ok { if v, ok := controllerComments[controllerName]; ok {
rootapi.Tags = append(rootapi.Tags, swagger.Tag{ rootapi.Tags = append(rootapi.Tags, swagger.Tag{
Name: controllerName, // if the NSInclude has no prefix, we use the controllername as the tag Name: controllerName, // if the NSInclude has no prefix, we use the controllername as the tag
@ -344,26 +386,39 @@ func GenerateDocs(curpath string) {
} }
} }
} }
os.Mkdir(path.Join(curpath, "swagger"), 0755)
fd, err := os.Create(path.Join(curpath, "swagger", "swagger.json")) if rootapiSingle {
if err != nil { rootapiMap[defaultNamespacePrefix] = &rootapiDefault
panic(err)
} }
fdyml, err := os.Create(path.Join(curpath, "swagger", "swagger.yml"))
if err != nil { for version, api := range rootapiMap {
panic(err) api.Definitions = rootapiDefault.Definitions
}
defer fdyml.Close() dir := path.Join(curpath, "swagger", version)
defer fd.Close() os.MkdirAll(dir, 0755)
dt, err := json.MarshalIndent(rootapi, "", " ")
dtyml, erryml := yaml.Marshal(rootapi) fd, err := os.Create(path.Join(dir, "swagger.json"))
if err != nil || erryml != nil { if err != nil {
panic(err) panic(err)
} }
_, err = fd.Write(dt) defer fd.Close()
_, erryml = fdyml.Write(dtyml)
if err != nil || erryml != nil { fdyml, err := os.Create(path.Join(dir, "swagger.yml"))
panic(err) if err != nil {
panic(err)
}
defer fdyml.Close()
dt, err := json.MarshalIndent(api, "", " ")
dtyml, erryml := yaml.Marshal(api)
if err != nil || erryml != nil {
panic(err)
}
_, err = fd.Write(dt)
_, erryml = fdyml.Write(dtyml)
if err != nil || erryml != nil {
panic(err)
}
} }
} }
@ -382,7 +437,7 @@ func analyseNewNamespace(ce *ast.CallExpr) (first string, others []ast.Expr) {
return return
} }
func analyseNSInclude(baseurl string, ce *ast.CallExpr) string { func analyseNSInclude(baseurl string, ce *ast.CallExpr, rootapi *swagger.Swagger) string {
cname := "" cname := ""
for _, p := range ce.Args { for _, p := range ce.Args {
var x *ast.SelectorExpr var x *ast.SelectorExpr
@ -964,16 +1019,16 @@ L:
if m.Title == "" { if m.Title == "" {
// Don't log when error has already been logged // Don't log when error has already been logged
if _, found := rootapi.Definitions[str]; !found { if _, found := rootapiDefault.Definitions[str]; !found {
beeLogger.Log.Warnf("Cannot find the object: %s", str) beeLogger.Log.Warnf("Cannot find the object: %s", str)
} }
m.Title = objectname m.Title = objectname
// TODO remove when all type have been supported // TODO remove when all type have been supported
} }
if len(rootapi.Definitions) == 0 { if len(rootapiDefault.Definitions) == 0 {
rootapi.Definitions = make(map[string]swagger.Schema) rootapiDefault.Definitions = make(map[string]swagger.Schema)
} }
rootapi.Definitions[str] = m rootapiDefault.Definitions[str] = m
return str, m, realTypes return str, m, realTypes
} }
@ -992,7 +1047,7 @@ func parseObject(imports []*ast.ImportSpec, d *ast.Object, k string, m *swagger.
m.Format = typeFormat[0] m.Format = typeFormat[0]
} else { } else {
objectName := packageName + "." + fmt.Sprint(t.Elt) objectName := packageName + "." + fmt.Sprint(t.Elt)
if _, ok := rootapi.Definitions[objectName]; !ok { if _, ok := rootapiDefault.Definitions[objectName]; !ok {
objectName, _, _ = getModel(objectName) objectName, _, _ = getModel(objectName)
} }
m.Items = &swagger.Schema{ m.Items = &swagger.Schema{