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

Add support for the attributes in struct fields #239

Merged
merged 1 commit into from
Nov 6, 2018
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,16 @@ Make it AND condition
// @Param default query string false "string default" default(A)
```

It also works for the struct fields:

```go
type Foo struct {
Bar string `minLength:"4" maxLength:"16"`
Baz int `minimum:"10" maximum:"20" default:"15"`
Qux []string `enums:"foo,bar,baz"`
}
```

### Available

Field Name | Type | Description
Expand Down
96 changes: 83 additions & 13 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,12 @@ type structField struct {
isRequired bool
crossPkg string
exampleValue interface{}
maximum *float64
minimum *float64
maxLength *int64
minLength *int64
enums []interface{}
defaultValue interface{}
}

func (parser *Parser) parseStruct(pkgName string, field *ast.Field) (properties map[string]spec.Schema) {
Expand Down Expand Up @@ -542,7 +548,13 @@ func (parser *Parser) parseStruct(pkgName string, field *ast.Field) (properties
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{structField.arrayType},
Type: []string{structField.arrayType},
Maximum: structField.maximum,
Minimum: structField.minimum,
MaxLength: structField.maxLength,
MinLength: structField.minLength,
Enum: structField.enums,
Default: structField.defaultValue,
},
},
},
Expand All @@ -563,6 +575,12 @@ func (parser *Parser) parseStruct(pkgName string, field *ast.Field) (properties
Description: desc,
Format: structField.formatType,
Required: required,
Maximum: structField.maximum,
Minimum: structField.minimum,
MaxLength: structField.maxLength,
MinLength: structField.minLength,
Enum: structField.enums,
Default: structField.defaultValue,
},
SwaggerSchemaProps: spec.SwaggerSchemaProps{
Example: structField.exampleValue,
Expand Down Expand Up @@ -590,6 +608,12 @@ func (parser *Parser) parseStruct(pkgName string, field *ast.Field) (properties
Format: structField.formatType,
Properties: props,
Required: nestRequired,
Maximum: structField.maximum,
Minimum: structField.minimum,
MaxLength: structField.maxLength,
MinLength: structField.minLength,
Enum: structField.enums,
Default: structField.defaultValue,
},
SwaggerSchemaProps: spec.SwaggerSchemaProps{
Example: structField.exampleValue,
Expand Down Expand Up @@ -655,8 +679,8 @@ func (parser *Parser) parseField(field *ast.Field) *structField {
return structField
}
// `json:"tag"` -> json:"tag"
structTag := strings.Replace(field.Tag.Value, "`", "", -1)
jsonTag := reflect.StructTag(structTag).Get("json")
structTag := reflect.StructTag(strings.Replace(field.Tag.Value, "`", "", -1))
jsonTag := structTag.Get("json")
// json:"tag,hoge"
if strings.Contains(jsonTag, ",") {
// json:",hoge"
Expand All @@ -672,8 +696,7 @@ func (parser *Parser) parseField(field *ast.Field) *structField {
structField.name = jsonTag
}

typeTag := reflect.StructTag(structTag).Get("swaggertype")
if typeTag != "" {
if typeTag := structTag.Get("swaggertype"); typeTag != "" {
parts := strings.Split(typeTag, ",")
if 0 < len(parts) && len(parts) <= 2 {
newSchemaType := parts[0]
Expand All @@ -688,35 +711,82 @@ func (parser *Parser) parseField(field *ast.Field) *structField {
structField.arrayType = newArrayType
}
}
exampleTag := reflect.StructTag(structTag).Get("example")
if exampleTag != "" {
if exampleTag := structTag.Get("example"); exampleTag != "" {
structField.exampleValue = defineTypeOfExample(structField.schemaType, structField.arrayType, exampleTag)
}
formatTag := reflect.StructTag(structTag).Get("format")
if formatTag != "" {
if formatTag := structTag.Get("format"); formatTag != "" {
structField.formatType = formatTag
}
bindingTag := reflect.StructTag(structTag).Get("binding")
if bindingTag != "" {
if bindingTag := structTag.Get("binding"); bindingTag != "" {
for _, val := range strings.Split(bindingTag, ",") {
if val == "required" {
structField.isRequired = true
break
}
}
}
validateTag := reflect.StructTag(structTag).Get("validate")
if validateTag != "" {
if validateTag := structTag.Get("validate"); validateTag != "" {
for _, val := range strings.Split(validateTag, ",") {
if val == "required" {
structField.isRequired = true
break
}
}
}
if enumsTag := structTag.Get("enums"); enumsTag != "" {
enumType := structField.schemaType
if structField.schemaType == "array" {
enumType = structField.arrayType
}

for _, e := range strings.Split(enumsTag, ",") {
structField.enums = append(structField.enums, defineType(enumType, e))
}
}
if defaultTag := structTag.Get("default"); defaultTag != "" {
structField.defaultValue = defineType(structField.schemaType, defaultTag)
}

if IsNumericType(structField.schemaType) || IsNumericType(structField.arrayType) {
structField.maximum = getFloatTag(structTag, "maximum")
structField.minimum = getFloatTag(structTag, "minimum")
}
if structField.schemaType == "string" || structField.arrayType == "string" {
structField.maxLength = getIntTag(structTag, "maxLength")
structField.minLength = getIntTag(structTag, "minLength")
}

return structField
}

func getFloatTag(structTag reflect.StructTag, tagName string) *float64 {
strValue := structTag.Get(tagName)
if strValue == "" {
return nil
}

value, err := strconv.ParseFloat(strValue, 64)
if err != nil {
panic(fmt.Errorf("can't parse numeric value of %q tag: %v", tagName, err))
}

return &value
}

func getIntTag(structTag reflect.StructTag, tagName string) *int64 {
strValue := structTag.Get(tagName)
if strValue == "" {
return nil
}

value, err := strconv.ParseInt(strValue, 10, 64)
if err != nil {
panic(fmt.Errorf("can't parse numeric value of %q tag: %v", tagName, err))
}

return &value
}

func toSnakeCase(in string) string {
runes := []rune(in)
length := len(runes)
Expand Down
24 changes: 23 additions & 1 deletion parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,8 @@ func TestParseSimpleApi(t *testing.T) {
},
"name": {
"type": "string",
"maxLength": 16,
"minLength": 4,
"example": "detail_category_name"
},
"photo_urls": {
Expand All @@ -546,6 +548,19 @@ func TestParseSimpleApi(t *testing.T) {
"decimal": {
"type": "number"
},
"enum_array": {
"type": "array",
"items": {
"type": "integer",
"enum": [
1,
2,
3,
5,
7
]
}
},
"id": {
"type": "integer",
"format": "int64",
Expand All @@ -563,6 +578,7 @@ func TestParseSimpleApi(t *testing.T) {
},
"is_alive": {
"type": "boolean",
"default": true,
"example": true
},
"name": {
Expand Down Expand Up @@ -593,10 +609,16 @@ func TestParseSimpleApi(t *testing.T) {
},
"price": {
"type": "number",
"maximum": 1000,
"minimum": 1,
"example": 3.25
},
"status": {
"type": "string"
"type": "string",
"enum": [
"healthy",
"ill"
]
},
"tags": {
"type": "array",
Expand Down
5 changes: 5 additions & 0 deletions schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ func IsPrimitiveType(typeName string) bool {
}
}

// IsNumericType determines whether the swagger type name is a numeric type
func IsNumericType(typeName string) bool {
return typeName == "integer" || typeName == "number"
}

// TransToValidSchemeType indicates type will transfer golang basic type to swagger supported type.
func TransToValidSchemeType(typeName string) string {
switch typeName {
Expand Down
9 changes: 5 additions & 4 deletions testdata/simple/web/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type Pet struct {
PhotoUrls []string `json:"photo_urls" example:"http://test/image/1.jpg,http://test/image/2.jpg" format:"url"`
SmallCategory struct {
ID int `json:"id" example:"1"`
Name string `json:"name" example:"detail_category_name" binding:"required"`
Name string `json:"name" example:"detail_category_name" binding:"required" minLength:"4" maxLength:"16"`
PhotoUrls []string `json:"photo_urls" example:"http://test/image/1.jpg,http://test/image/2.jpg"`
} `json:"small_category"`
} `json:"category"`
Expand All @@ -25,14 +25,15 @@ type Pet struct {
Tags []Tag `json:"tags"`
Pets *[]Pet2 `json:"pets"`
Pets2 []*Pet2 `json:"pets2"`
Status string `json:"status"`
Price float32 `json:"price" example:"3.25"`
IsAlive bool `json:"is_alive" example:"true"`
Status string `json:"status" enums:"healthy,ill"`
Price float32 `json:"price" example:"3.25" minimum:"1.0" maximum:"1000"`
IsAlive bool `json:"is_alive" example:"true" default:"true"`
Data interface{} `json:"data"`
Hidden string `json:"-"`
UUID uuid.UUID `json:"uuid"`
Decimal decimal.Decimal `json:"decimal"`
IntArray []int `json:"int_array" example:"1,2"`
EnumArray []int `json:"enum_array" enums:"1,2,3,5,7"`
}

type Tag struct {
Expand Down