Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat multi namespace gen swagger #866

Merged
merged 2 commits into from
Apr 10, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
178 changes: 145 additions & 33 deletions generate/swaggergen/g_docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@
package swaggergen

import (
"bufio"
"encoding/json"
"errors"
"fmt"
"go/ast"
"go/build"
"go/parser"
"go/token"
"io"
"os"
"path"
"path/filepath"
Expand Down Expand Up @@ -56,12 +58,17 @@ const (
astTypeMap = "map"
)

const defaultNamespacePrefix = ""

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 rootapiSingle = false
var rootapiDefault swagger.Swagger
var rootapiMap map[string]*swagger.Swagger // version, swagger
var astPkgs []*ast.Package
var pkgLoadedCache map[string]struct{}

Expand Down Expand Up @@ -106,6 +113,7 @@ func init() {
importlist = make(map[string]string)
controllerList = make(map[string]map[string]*swagger.Item)
modelsList = make(map[string]map[string]swagger.Schema)
rootapiMap = make(map[string]*swagger.Swagger)
astPkgs = make([]*ast.Package, 0)
pkgLoadedCache = make(map[string]struct{})
}
Expand Down Expand Up @@ -188,14 +196,21 @@ func GenerateDocs(curpath string) {
beeLogger.Log.Fatalf("Error while parsing router.go: %s", err)
}

rootapi.Infos = swagger.Information{}
rootapi.SwaggerVersion = "2.0"

// Analyse API comments
if f.Comments != nil {
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") {
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"):])
} else if strings.HasPrefix(s, "@Title") {
rootapi.Infos.Title = strings.TrimSpace(s[len("@Title"):])
Expand Down Expand Up @@ -279,6 +294,12 @@ func GenerateDocs(curpath string) {
rootapi.Security = append(rootapi.Security, getSecurity(s))
}
}

_, namespacePrefixExist := rootapiMap[namespacePrefix]
if !namespacePrefixExist && !rootapiSingle {
rootapiSingle = true
rootapiDefault = rootapi
}
}
}
// Analyse controller package
Expand All @@ -289,6 +310,7 @@ func GenerateDocs(curpath string) {
}
analyseControllerPkg(localName, im.Path.Value)
}

for _, d := range f.Decls {
switch specDecl := d.(type) {
case *ast.FuncDecl:
Expand All @@ -302,21 +324,43 @@ func GenerateDocs(curpath string) {
if !selOK || selExpr.Sel.Name != "NewNamespace" {
continue
}

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 rootapi.BasePath == "" {
rootapi.BasePath = version
}

for _, p := range params {
switch pp := p.(type) {
case *ast.CallExpr:
var controllerName string
if selname := pp.Fun.(*ast.SelectorExpr).Sel.String(); selname == "NSNamespace" {
s, params := analyseNewNamespace(pp)
if rootapi.BasePath == "" {
s = path.Join(version, s)
}

for _, sp := range params {
switch pp := sp.(type) {
case *ast.CallExpr:
if pp.Fun.(*ast.SelectorExpr).Sel.String() == "NSInclude" {
controllerName = analyseNSInclude(s, pp)
controllerName = analyseNSInclude(s, pp, rootapi)
if v, ok := controllerComments[controllerName]; ok {
rootapi.Tags = append(rootapi.Tags, swagger.Tag{
Name: strings.Trim(s, "/"),
Expand All @@ -327,7 +371,7 @@ func GenerateDocs(curpath string) {
}
}
} else if selname == "NSInclude" {
controllerName = analyseNSInclude("", pp)
controllerName = analyseNSInclude("", pp, rootapi)
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
Expand All @@ -344,27 +388,42 @@ func GenerateDocs(curpath string) {
}
}
}
os.Mkdir(path.Join(curpath, "swagger"), 0755)
fd, err := os.Create(path.Join(curpath, "swagger", "swagger.json"))
if err != nil {
panic(err)
}
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)

if rootapiSingle {
rootapiMap[defaultNamespacePrefix] = &rootapiDefault
}
_, err = fd.Write(dt)
_, erryml = fdyml.Write(dtyml)
if err != nil || erryml != nil {
panic(err)

for version, api := range rootapiMap {
api.Definitions = rootapiDefault.Definitions

dir := path.Join(curpath, "swagger", version)
os.MkdirAll(dir, 0755)

fd, err := os.Create(path.Join(dir, "swagger.json"))
if err != nil {
panic(err)
}
defer fd.Close()

fdyml, err := os.Create(path.Join(dir, "swagger.yml"))
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)
}
}

modifySwaggerIndexFile(curpath, rootapiSingle, rootapiMap)
}

// analyseNewNamespace returns version and the others params
Expand All @@ -382,7 +441,7 @@ func analyseNewNamespace(ce *ast.CallExpr) (first string, others []ast.Expr) {
return
}

func analyseNSInclude(baseurl string, ce *ast.CallExpr) string {
func analyseNSInclude(baseurl string, ce *ast.CallExpr, rootapi *swagger.Swagger) string {
cname := ""
for _, p := range ce.Args {
var x *ast.SelectorExpr
Expand Down Expand Up @@ -964,16 +1023,16 @@ L:

if m.Title == "" {
// 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)
}
m.Title = objectname
// TODO remove when all type have been supported
}
if len(rootapi.Definitions) == 0 {
rootapi.Definitions = make(map[string]swagger.Schema)
if len(rootapiDefault.Definitions) == 0 {
rootapiDefault.Definitions = make(map[string]swagger.Schema)
}
rootapi.Definitions[str] = m
rootapiDefault.Definitions[str] = m
return str, m, realTypes
}

Expand All @@ -992,7 +1051,7 @@ func parseObject(imports []*ast.ImportSpec, d *ast.Object, k string, m *swagger.
m.Format = typeFormat[0]
} else {
objectName := packageName + "." + fmt.Sprint(t.Elt)
if _, ok := rootapi.Definitions[objectName]; !ok {
if _, ok := rootapiDefault.Definitions[objectName]; !ok {
objectName, _, _ = getModel(objectName)
}
m.Items = &swagger.Schema{
Expand Down Expand Up @@ -1453,3 +1512,56 @@ func checkAndLoadPackage(imports []*ast.ImportSpec, realType, curPkgName string)

pkgLoadedCache[pkgPath] = struct{}{}
}

func modifySwaggerIndexFile(curpath string, rootapiSingle bool, rootapiMap map[string]*swagger.Swagger) {
swaggerIndexFilename := "index.html"
swaggerIndexFullname := path.Join(curpath, "swagger", swaggerIndexFilename)

swaggerJsonFilename := "swagger.json"
swaggerUIBundleSign := `SwaggerUIBundle(`
var swaggerUIBundleSignPass bool

if _, err := os.Stat(swaggerIndexFullname); !os.IsNotExist(err) {
var swaggerJsonUrl string
if rootapiSingle {
swaggerJsonUrl = fmt.Sprintf(` url: "%s",`, swaggerJsonFilename)
} else {
urls := make([]string, 0)
for namespace, _ := range rootapiMap {
namespace = strings.TrimLeft(namespace, "/")
urls = append(urls, fmt.Sprintf(`{url: "%s", name: "%s"}`, path.Join(namespace, swaggerJsonFilename), namespace))
}
swaggerJsonUrl = fmt.Sprintf(` urls: [%s],`, strings.Join(urls, ", "))
}

var indexFileContent string
if f, err := os.Open(swaggerIndexFullname); err == nil {
defer f.Close()

buf := bufio.NewReader(f)
for {
line, _, c := buf.ReadLine()
if c == io.EOF {
break
}

if strings.Contains(string(line), swaggerUIBundleSign) {
swaggerUIBundleSignPass = true
}
if swaggerUIBundleSignPass && (strings.Contains(string(line), "url:") || strings.Contains(string(line), "urls:")) {
indexFileContent += swaggerJsonUrl + "\n"
} else {
indexFileContent += string(line) + "\n"
}
}
}

if fw, err := os.OpenFile(swaggerIndexFullname, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666); err == nil {
defer fw.Close()

w := bufio.NewWriter(fw)
w.WriteString(indexFileContent)
w.Flush()
}
}
}
Loading