Skip to content

Commit

Permalink
Merge pull request #39 from suyuan32/feat-validator-gen
Browse files Browse the repository at this point in the history
Feat: validator gen
  • Loading branch information
suyuan32 authored Apr 3, 2023
2 parents 23fea24 + fbe5779 commit f5aeb10
Show file tree
Hide file tree
Showing 5 changed files with 376 additions and 5 deletions.
19 changes: 18 additions & 1 deletion tools/goctl/api/gogen/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,29 @@ func genFile(c fileGenConfig) error {

func writeProperty(writer io.Writer, name, tag, comment string, tp spec.Type, doc spec.Doc, indent int) error {
// write doc for swagger
var err error
hasValidator := false

for _, v := range doc {
_, _ = fmt.Fprintf(writer, "\t%s\n", v)
if util.HasCustomValidation(v) {
hasValidator = true
}
}

if !hasValidator {
validatorComment, err := util.ConvertValidateTagToSwagger(tag)
if err != nil {
return err
}

for _, v := range validatorComment {
_, _ = fmt.Fprint(writer, v)
}
}

util.WriteIndent(writer, indent)
var err error

if len(comment) > 0 {
comment = strings.TrimPrefix(comment, "//")
comment = "//" + comment
Expand Down
125 changes: 125 additions & 0 deletions tools/goctl/api/util/validator_tag_converter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// Copyright 2023 The Ryan SU Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package util

import (
"errors"
"fmt"
"math"
"strconv"
"strings"
)

// ConvertValidateTagToSwagger converts the validator tag to swagger comments.
func ConvertValidateTagToSwagger(tagData string) ([]string, error) {
if tagData == "" || !strings.Contains(tagData, "validate") {
return nil, nil
}

validateData := ExtractValidateString(tagData)

return ConvertTagToComment(validateData)
}

// ExtractValidateString extracts the validator's string.
func ExtractValidateString(data string) string {
beginIndex := strings.Index(data, "validate")
if beginIndex == -1 {
return ""
}
firstQuotationMark := 0
for i := beginIndex; i < len(data); i++ {
if data[i] == '"' && firstQuotationMark == 0 {
firstQuotationMark = i
} else if data[i] == '"' && firstQuotationMark != 0 {
return data[firstQuotationMark+1 : i]
}
}
return ""
}

// ConvertTagToComment converts validator tag to comments.
func ConvertTagToComment(tagString string) ([]string, error) {
var result []string
vals := strings.Split(tagString, ",")
for _, v := range vals {
if strings.Contains(v, "required") {
result = append(result, "// required : true\n")
}

if strings.Contains(v, "min") || strings.Contains(v, "max") {
result = append(result, fmt.Sprintf("// %s\n", strings.Replace(v, "=", " length : ", -1)))
}

if strings.Contains(v, "len") {
tagSplit := strings.Split(v, "=")
_, tagNum := tagSplit[0], tagSplit[1]
result = append(result, fmt.Sprintf("// max length : %s\n", tagNum))
result = append(result, fmt.Sprintf("// min length : %s\n", tagNum))
}

if strings.Contains(v, "gt") || strings.Contains(v, "gte") ||
strings.Contains(v, "lt") || strings.Contains(v, "lte") {
tagSplit := strings.Split(v, "=")
tag, tagNum := tagSplit[0], tagSplit[1]
if strings.Contains(tagNum, ".") {
bitSize := len(tagNum) - strings.Index(tagNum, ".") - 1
n, err := strconv.ParseFloat(tagNum, bitSize)
if err != nil {
return nil, errors.New("failed to convert the number in validate tag")
}

switch tag {
case "gte":
result = append(result, fmt.Sprintf("// min : %.*f\n", bitSize, n))
case "gt":
result = append(result, fmt.Sprintf("// min : %.*f\n", bitSize, n+1/math.Pow(10, float64(bitSize))))
case "lte":
result = append(result, fmt.Sprintf("// max : %.*f\n", bitSize, n))
case "lt":
result = append(result, fmt.Sprintf("// max : %.*f\n", bitSize, n-1/math.Pow(10, float64(bitSize))))
}
} else {
n, err := strconv.Atoi(tagNum)
if err != nil {
return nil, errors.New("failed to convert the number in validate tag")
}

switch tag {
case "gte":
result = append(result, fmt.Sprintf("// min : %d\n", n))
case "gt":
result = append(result, fmt.Sprintf("// min : %d\n", n))
case "lte":
result = append(result, fmt.Sprintf("// max : %d\n", n))
case "lt":
result = append(result, fmt.Sprintf("// max : %d\n", n))
}
}

}
}
return result, nil
}

// HasCustomValidation returns true if the comment has validations.
func HasCustomValidation(data string) bool {
lowerCase := strings.ToLower(data)
if strings.Contains(lowerCase, "max") || strings.Contains(lowerCase, "min") ||
strings.Contains(lowerCase, "required") {
return true
}
return false
}
67 changes: 67 additions & 0 deletions tools/goctl/api/util/validator_tag_converter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package util

import (
"reflect"
"testing"
)

func TestConvertValidateTagToSwagger(t *testing.T) {
type args struct {
tagData string
}
tests := []struct {
name string
args args
want []string
wantErr bool
}{
{
name: "testString",
args: args{"json:\"path,optional\" validate:\"omitempty,min=1,max=50\""},
want: []string{"// min length : 1\n", "// max length : 50\n"},
wantErr: false,
},
{
name: "testLen",
args: args{"json:\"path,optional\" validate:\"omitempty,len=50\""},
want: []string{"// max length : 50\n", "// min length : 50\n"},
wantErr: false,
},
{
name: "testNum",
args: args{"json:\"path,optional\" validate:\"omitempty,gte=1,lte=50\""},
want: []string{"// min : 1\n", "// max : 50\n"},
wantErr: false,
},
{
name: "testFloat",
args: args{"json:\"path,optional\" validate:\"omitempty,gte=1.1,lte=50.0\""},
want: []string{"// min : 1.1\n", "// max : 50.0\n"},
wantErr: false,
},
{
name: "testFloat2",
args: args{"json:\"path,optional\" validate:\"omitempty,gt=1.11,lt=50.01\""},
want: []string{"// min : 1.12\n", "// max : 50.00\n"},
wantErr: false,
},
{
name: "testRequired",
args: args{"json:\"path,optional\" validate:\"required,gte=1.1,lte=50.0\""},
want: []string{"// required : true\n", "// min : 1.1\n", "// max : 50.0\n"},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ConvertValidateTagToSwagger(tt.args.tagData)
if (err != nil) != tt.wantErr {
t.Errorf("ConvertValidateTagToSwagger() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("ConvertValidateTagToSwagger() got = %v, want %v", got, tt.want)
}
})
}
}
109 changes: 105 additions & 4 deletions tools/goctl/frontend/vben/gendata.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,16 @@ import (
"bytes"
"errors"
"fmt"
"math"
"path/filepath"
"strconv"
"strings"
"text/template"

"github.com/iancoleman/strcase"

"github.com/zeromicro/go-zero/tools/goctl/api/spec"
util2 "github.com/zeromicro/go-zero/tools/goctl/api/util"
"github.com/zeromicro/go-zero/tools/goctl/util"
)

Expand Down Expand Up @@ -68,12 +71,13 @@ func genData(g *GenContext) error {
strcase.ToLowerCamel(strings.TrimSuffix(specData.RawName, "Info")),
strcase.ToLowerCamel(val.Name)), strcase.ToLowerCamel(val.Name)))

formData.WriteString(fmt.Sprintf("\n {\n field: '%s',\n label: t('%s'),\n %s\n required: true,\n },",
formData.WriteString(fmt.Sprintf("\n {\n field: '%s',\n label: t('%s'),\n %s\n required: true,\n%s },",
strcase.ToLowerCamel(val.Name),
fmt.Sprintf("%s.%s.%s", g.FolderName,
strcase.ToLowerCamel(strings.TrimSuffix(specData.RawName, "Info")),
strcase.ToLowerCamel(val.Name)),
getComponent(val.Type.Name()),
GetRules(val),
))
}
}
Expand All @@ -93,11 +97,11 @@ func genData(g *GenContext) error {

for _, val := range specData.Members {
if val.Name != "" {
searchFormData.WriteString(fmt.Sprintf("\n {\n field: '%s',\n label: t('%s'),\n component: 'Input',\n colProps: { span: 8 },\n },",
searchFormData.WriteString(fmt.Sprintf("\n {\n field: '%s',\n label: t('%s'),\n component: 'Input',\n colProps: { span: 8 },\n%s },",
strcase.ToLowerCamel(val.Name),
fmt.Sprintf("%s.%s.%s", g.FolderName,
strcase.ToLowerCamel(strings.TrimSuffix(specData.RawName, "ListReq")),
strcase.ToLowerCamel(val.Name)),
strcase.ToLowerCamel(val.Name)), GetRules(val),
))
}
}
Expand Down Expand Up @@ -125,11 +129,108 @@ func getComponent(dataType string) string {
switch dataType {
case "string":
return "component: 'Input',"
case "int32", "int64", "uint32", "uint64", "float32", "float64":
case "int", "uint", "int8", "uint8", "int16", "uint16", "int32", "int64", "uint32", "uint64", "float32", "float64":
return "component: 'InputNumber',"
case "bool":
return "component: 'RadioButtonGroup',\n defaultValue: false,\n componentProps: {\n options: [\n { label: t('common.on'), value: false },\n { label: t('common.off'), value: true },\n ],\n },"
default:
return "component: 'Input',"
}
}

// GetRules returns the rules from tag.
func GetRules(t spec.Member) string {
validatorString := util2.ExtractValidateString(t.Tag)
if validatorString == "" {
return ""
}

rules, err := ConvertTagToRules(validatorString)
if err != nil {
return ""
}

switch GetRuleType(t.Type.Name()) {
case "string":
return fmt.Sprintf(" rules: [{ %s }],\n", strings.Join(rules, ", "))
case "number":
return fmt.Sprintf(" rules: [{ type: 'number', %s }],\n", strings.Join(rules, ", "))
case "float":
return fmt.Sprintf(" rules: [{ type: 'float', %s }],\n", strings.Join(rules, ", "))
default:
return ""
}
}

// GetRuleType returns the rule type from go type.
func GetRuleType(t string) string {
switch t {
case "string":
return "string"
case "int", "uint", "int8", "uint8", "int16", "uint16", "int32", "int64", "uint32", "uint64":
return "number"
case "float32", "float64":
return "float"
default:
return "string"
}
}

// ConvertTagToRules converts validator tag to rules.
func ConvertTagToRules(tagString string) ([]string, error) {
var result []string
vals := strings.Split(tagString, ",")
for _, v := range vals {
if strings.Contains(v, "min") || strings.Contains(v, "max") {
result = append(result, strings.Replace(v, "=", ": ", -1))
}

if strings.Contains(v, "len") {
tagSplit := strings.Split(v, "=")
_, tagNum := tagSplit[0], tagSplit[1]
result = append(result, fmt.Sprintf("len: %s", tagNum))
}

if strings.Contains(v, "gt") || strings.Contains(v, "gte") ||
strings.Contains(v, "lt") || strings.Contains(v, "lte") {
tagSplit := strings.Split(v, "=")
tag, tagNum := tagSplit[0], tagSplit[1]
if strings.Contains(tagNum, ".") {
bitSize := len(tagNum) - strings.Index(tagNum, ".") - 1
n, err := strconv.ParseFloat(tagNum, bitSize)
if err != nil {
return nil, errors.New("failed to convert the number in validate tag")
}

switch tag {
case "gte":
result = append(result, fmt.Sprintf("min: %.*f", bitSize, n))
case "gt":
result = append(result, fmt.Sprintf("min: %.*f", bitSize, n+1/math.Pow(10, float64(bitSize))))
case "lte":
result = append(result, fmt.Sprintf("max: %.*f", bitSize, n))
case "lt":
result = append(result, fmt.Sprintf("max: %.*f", bitSize, n-1/math.Pow(10, float64(bitSize))))
}
} else {
n, err := strconv.Atoi(tagNum)
if err != nil {
return nil, errors.New("failed to convert the number in validate tag")
}

switch tag {
case "gte":
result = append(result, fmt.Sprintf("min: %d", n))
case "gt":
result = append(result, fmt.Sprintf("min: %d", n))
case "lte":
result = append(result, fmt.Sprintf("max: %d", n))
case "lt":
result = append(result, fmt.Sprintf("max: %d", n))
}
}

}
}
return result, nil
}
Loading

0 comments on commit f5aeb10

Please sign in to comment.