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

parse global enums #1387

Merged
merged 8 commits into from
Nov 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
122 changes: 122 additions & 0 deletions const.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package swag

import (
"go/ast"
"go/token"
"strconv"
)

// ConstVariable a model to record an const variable
type ConstVariable struct {
Name *ast.Ident
Type ast.Expr
Value interface{}
Comment *ast.CommentGroup
}

// EvaluateValue evaluate the value
func (cv *ConstVariable) EvaluateValue(constTable map[string]*ConstVariable) interface{} {
if expr, ok := cv.Value.(ast.Expr); ok {
value, evalType := evaluateConstValue(cv.Name.Name, cv.Name.Obj.Data.(int), expr, constTable, make(map[string]struct{}))
if cv.Type == nil && evalType != nil {
cv.Type = evalType
}
if value != nil {
cv.Value = value
}
return value
}
return cv.Value
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The behavior of this function is quite unusual. it value is an ast.Expresion , it populates the ConstVariable type and value , otherwise, it returns a value that may be ni ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cv.Value is initialized in the func collectConstVariables with a ast.Expr, and then will be reassigned with the evaluated non-nil value of integer or string type,so it would never be nil.


func evaluateConstValue(name string, iota int, expr ast.Expr, constTable map[string]*ConstVariable, recursiveStack map[string]struct{}) (interface{}, ast.Expr) {
if len(name) > 0 {
if _, ok := recursiveStack[name]; ok {
return nil, nil
}
recursiveStack[name] = struct{}{}
}

switch valueExpr := expr.(type) {
case *ast.Ident:
if valueExpr.Name == "iota" {
return iota, nil
}
if constTable != nil {
if cv, ok := constTable[valueExpr.Name]; ok {
if expr, ok = cv.Value.(ast.Expr); ok {
value, evalType := evaluateConstValue(valueExpr.Name, cv.Name.Obj.Data.(int), expr, constTable, recursiveStack)
if cv.Type == nil {
cv.Type = evalType
}
if value != nil {
cv.Value = value
}
return value, evalType
}
return cv.Value, cv.Type
}
}
case *ast.BasicLit:
switch valueExpr.Kind {
case token.INT:
x, err := strconv.ParseInt(valueExpr.Value, 10, 64)
if err != nil {
return nil, nil
}
return int(x), nil
case token.STRING, token.CHAR:
return valueExpr.Value[1 : len(valueExpr.Value)-1], nil
}
case *ast.UnaryExpr:
x, evalType := evaluateConstValue("", iota, valueExpr.X, constTable, recursiveStack)
switch valueExpr.Op {
case token.SUB:
return -x.(int), evalType
case token.XOR:
return ^(x.(int)), evalType
}
case *ast.BinaryExpr:
x, evalTypex := evaluateConstValue("", iota, valueExpr.X, constTable, recursiveStack)
y, evalTypey := evaluateConstValue("", iota, valueExpr.Y, constTable, recursiveStack)
evalType := evalTypex
if evalType == nil {
evalType = evalTypey
}
switch valueExpr.Op {
case token.ADD:
if ix, ok := x.(int); ok {
return ix + y.(int), evalType
} else if sx, ok := x.(string); ok {
return sx + y.(string), evalType
}
case token.SUB:
return x.(int) - y.(int), evalType
case token.MUL:
return x.(int) * y.(int), evalType
case token.QUO:
return x.(int) / y.(int), evalType
case token.REM:
return x.(int) % y.(int), evalType
case token.AND:
return x.(int) & y.(int), evalType
case token.OR:
return x.(int) | y.(int), evalType
case token.XOR:
return x.(int) ^ y.(int), evalType
case token.SHL:
return x.(int) << y.(int), evalType
case token.SHR:
return x.(int) >> y.(int), evalType
}
case *ast.ParenExpr:
return evaluateConstValue("", iota, valueExpr.X, constTable, recursiveStack)
case *ast.CallExpr:
//data conversion
if ident, ok := valueExpr.Fun.(*ast.Ident); ok && len(valueExpr.Args) == 1 && IsGolangPrimitiveType(ident.Name) {
arg, _ := evaluateConstValue("", iota, valueExpr.Args[0], constTable, recursiveStack)
return arg, nil
}
}
return nil, nil
}
13 changes: 13 additions & 0 deletions enums.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package swag

const (
enumVarNamesExtension = "x-enum-varnames"
enumCommentsExtension = "x-enum-comments"
)

// EnumValue a model to record an enum const variable
type EnumValue struct {
key string
Value interface{}
Comment string
}
23 changes: 23 additions & 0 deletions enums_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package swag

import (
"encoding/json"
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
)

func TestParseGlobalEnums(t *testing.T) {
searchDir := "testdata/enums"
expected, err := os.ReadFile(filepath.Join(searchDir, "expected.json"))
assert.NoError(t, err)

p := New()
err = p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
b, err := json.MarshalIndent(p.swagger, "", " ")
assert.NoError(t, err)
assert.Equal(t, string(expected), string(b))
}
4 changes: 2 additions & 2 deletions field_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -405,13 +405,13 @@ func (ps *tagBaseFieldParser) ComplementSchema(schema *spec.Schema) error {
if schema.Items.Schema.Extensions == nil {
schema.Items.Schema.Extensions = map[string]interface{}{}
}
schema.Items.Schema.Extensions["x-enum-varnames"] = field.enumVarNames
schema.Items.Schema.Extensions[enumVarNamesExtension] = field.enumVarNames
} else {
// Add to top level schema
if schema.Extensions == nil {
schema.Extensions = map[string]interface{}{}
}
schema.Extensions["x-enum-varnames"] = field.enumVarNames
schema.Extensions[enumVarNamesExtension] = field.enumVarNames
}
}

Expand Down
58 changes: 58 additions & 0 deletions package.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package swag

import "go/ast"

// PackageDefinitions files and definition in a package.
type PackageDefinitions struct {
// files in this package, map key is file's relative path starting package path
Files map[string]*ast.File

// definitions in this package, map key is typeName
TypeDefinitions map[string]*TypeSpecDef

// const variables in this package, map key is the name
ConstTable map[string]*ConstVariable

// const variables in order in this package
OrderedConst []*ConstVariable

// package name
Name string
}

// NewPackageDefinitions new a PackageDefinitions object
func NewPackageDefinitions(name string) *PackageDefinitions {
return &PackageDefinitions{
Name: name,
Files: make(map[string]*ast.File),
TypeDefinitions: make(map[string]*TypeSpecDef),
ConstTable: make(map[string]*ConstVariable),
}
}

// AddFile add a file
func (pkg *PackageDefinitions) AddFile(pkgPath string, file *ast.File) *PackageDefinitions {
pkg.Files[pkgPath] = file
return pkg
}

// AddTypeSpec add a type spec.
func (pkg *PackageDefinitions) AddTypeSpec(name string, typeSpec *TypeSpecDef) *PackageDefinitions {
pkg.TypeDefinitions[name] = typeSpec
return pkg
}

// AddConst add a const variable.
func (pkg *PackageDefinitions) AddConst(valueSpec *ast.ValueSpec) *PackageDefinitions {
for i := 0; i < len(valueSpec.Names) && i < len(valueSpec.Values); i++ {
variable := &ConstVariable{
Name: valueSpec.Names[i],
Type: valueSpec.Type,
Value: valueSpec.Values[i],
Comment: valueSpec.Comment,
}
pkg.ConstTable[valueSpec.Names[i].Name] = variable
pkg.OrderedConst = append(pkg.OrderedConst, variable)
}
return pkg
}