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: allow enum ordered const name override #1779

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
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
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -900,6 +900,36 @@ Make it OR condition
// @Security OAuth2Application[write, admin] || APIKeyAuth
```

### Generate enum types from enum constants

You can generate enums from ordered constants. Each enum variant can have a comment, an override name, or both. This works with both iota-defined and manually defined constants.

```go
type Difficulty string

const (
Easy Difficulty = "easy" // You can add a comment to the enum variant.
Medium Difficulty = "medium" // @name MediumDifficulty
Hard Difficulty = "hard" // @name HardDifficulty You can have a name override and a comment.
)

type Class int

const (
First Class = iota // @name FirstClass
Second // Name override and comment rules apply here just as above.
Third // @name ThirdClass This one has a name override and a comment.
)

// There is no need to add `enums:"..."` to the fields, it is automatically generated from the ordered consts.
type Quiz struct {
Difficulty Difficulty
Class Class
Questions []string
Answers []string
}
```


### Add a description for enum items

Expand Down
13 changes: 13 additions & 0 deletions const.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,19 @@ type ConstVariable struct {
Pkg *PackageDefinitions
}

// VariableName gets the bane for this const variable, taking into account comment overrides.
func (cv *ConstVariable) VariableName() string {
if ignoreNameOverride(cv.Name.Name) {
return cv.Name.Name[1:]
}

if overriddenName := nameOverride(cv.Comment); overriddenName != "" {
return overriddenName
}

return cv.Name.Name
}

var escapedChars = map[uint8]uint8{
'n': '\n',
'r': '\r',
Expand Down
11 changes: 11 additions & 0 deletions enums_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ func TestParseGlobalEnums(t *testing.T) {
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))

constsPath := "github.com/swaggo/swag/testdata/enums/consts"
assert.Equal(t, 64, p.packages.packages[constsPath].ConstTable["uintSize"].Value)
assert.Equal(t, int32(62), p.packages.packages[constsPath].ConstTable["maxBase"].Value)
Expand All @@ -30,4 +32,13 @@ func TestParseGlobalEnums(t *testing.T) {
assert.Equal(t, "aa\nbb\u8888cc", p.packages.packages[constsPath].ConstTable["escapestr"].Value)
assert.Equal(t, 1_000_000, p.packages.packages[constsPath].ConstTable["underscored"].Value)
assert.Equal(t, 0b10001000, p.packages.packages[constsPath].ConstTable["binaryInteger"].Value)

typesPath := "github.com/swaggo/swag/testdata/enums/types"
difficultyEnums := p.packages.packages[typesPath].TypeDefinitions["Difficulty"].Enums
assert.Equal(t, "Easy", difficultyEnums[0].key)
assert.Equal(t, "", difficultyEnums[0].Comment)
assert.Equal(t, "Medium", difficultyEnums[1].key)
assert.Equal(t, "This one also has a comment", difficultyEnums[1].Comment)
assert.Equal(t, "DifficultyHard", difficultyEnums[2].key)
assert.Equal(t, "This means really hard", difficultyEnums[2].Comment)
}
14 changes: 4 additions & 10 deletions packages.go
Original file line number Diff line number Diff line change
Expand Up @@ -362,21 +362,15 @@ func (pkgDefs *PackagesDefinitions) collectConstEnums(parsedSchemas map[*TypeSpe
typeDef.Enums = make([]EnumValue, 0)
}

name := constVar.Name.Name
name := constVar.VariableName()
if _, ok = constVar.Value.(ast.Expr); ok {
continue
}

enumValue := EnumValue{
key: name,
Value: constVar.Value,
}
if constVar.Comment != nil && len(constVar.Comment.List) > 0 {
enumValue.Comment = constVar.Comment.List[0].Text
enumValue.Comment = strings.TrimPrefix(enumValue.Comment, "//")
enumValue.Comment = strings.TrimPrefix(enumValue.Comment, "/*")
enumValue.Comment = strings.TrimSuffix(enumValue.Comment, "*/")
enumValue.Comment = strings.TrimSpace(enumValue.Comment)
key: name,
Value: constVar.Value,
Comment: commentWithoutNameOverride(constVar.Comment),
}
typeDef.Enums = append(typeDef.Enums, enumValue)
}
Expand Down
41 changes: 41 additions & 0 deletions schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import (
"errors"
"fmt"
"github.com/go-openapi/spec"
"go/ast"
"regexp"
"strings"
)

const (
Expand Down Expand Up @@ -134,6 +137,44 @@ func ignoreNameOverride(name string) bool {
return len(name) != 0 && name[0] == IgnoreNameOverridePrefix
}

var overrideNameRegex = regexp.MustCompile(`(?i)^@name\s+(\S+)`)

func nameOverride(commentGroup *ast.CommentGroup) string {
if commentGroup == nil {
return ""
}

// get alias from comment '// @name '
for _, comment := range commentGroup.List {
trimmedComment := strings.TrimSpace(strings.TrimLeft(comment.Text, "/"))
texts := overrideNameRegex.FindStringSubmatch(trimmedComment)
if len(texts) > 1 {
return texts[1]
}
}

return ""
}

func commentWithoutNameOverride(commentGroup *ast.CommentGroup) string {
if commentGroup == nil {
return ""
}

commentBuilder := strings.Builder{}
for _, comment := range commentGroup.List {
commentText := comment.Text
commentText = strings.TrimPrefix(commentText, "//")
commentText = strings.TrimPrefix(commentText, "/*")
commentText = strings.TrimSuffix(commentText, "*/")
commentText = strings.TrimSpace(commentText)
commentText = overrideNameRegex.ReplaceAllString(commentText, "")
commentText = strings.TrimSpace(commentText)
commentBuilder.WriteString(commentText)
}
return commentBuilder.String()
}

// IsComplexSchema whether a schema is complex and should be a ref schema
func IsComplexSchema(schema *spec.Schema) bool {
// a enum type should be complex
Expand Down
8 changes: 8 additions & 0 deletions testdata/enums/types/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,11 @@ type PersonWithArrayEnum struct {
Mask []Mask
Type Type
}

type Difficulty string

const (
DifficultyEasy Difficulty = "easy" // @name Easy
DifficultyMedium Difficulty = "medium" // @Name Medium This one also has a comment
DifficultyHard Difficulty = "hard" // This means really hard
)
19 changes: 4 additions & 15 deletions types.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package swag
import (
"go/ast"
"go/token"
"regexp"
"strings"

"github.com/go-openapi/spec"
Expand Down Expand Up @@ -46,20 +45,10 @@ func (t *TypeSpecDef) Name() string {
func (t *TypeSpecDef) TypeName() string {
if ignoreNameOverride(t.TypeSpec.Name.Name) {
return t.TypeSpec.Name.Name[1:]
} else if t.TypeSpec.Comment != nil {
// get alias from comment '// @name '
const regexCaseInsensitive = "(?i)"
reTypeName, err := regexp.Compile(regexCaseInsensitive + `^@name\s+(\S+)`)
if err != nil {
panic(err)
}
for _, comment := range t.TypeSpec.Comment.List {
trimmedComment := strings.TrimSpace(strings.TrimLeft(comment.Text, "/"))
texts := reTypeName.FindStringSubmatch(trimmedComment)
if len(texts) > 1 {
return texts[1]
}
}
}

if overriddenName := nameOverride(t.TypeSpec.Comment); overriddenName != "" {
return overriddenName
}

var names []string
Expand Down