Skip to content

Commit

Permalink
fix issue #2890 (#3002)
Browse files Browse the repository at this point in the history
  • Loading branch information
gqcn committed Oct 9, 2023
1 parent d6362ca commit 8d925d4
Show file tree
Hide file tree
Showing 9 changed files with 140 additions and 71 deletions.
55 changes: 55 additions & 0 deletions net/ghttp/ghttp_z_unit_issue_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/test/gtest"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gtag"
"github.com/gogf/gf/v2/util/guid"
)

Expand Down Expand Up @@ -365,3 +366,57 @@ func Test_Issue2482(t *testing.T) {
t.Assert(c.PutContent(ctx, "/api/v2/order", content), `{"code":0,"message":"","data":null}`)
})
}

type Issue2890Enum string

const (
Issue2890EnumA Issue2890Enum = "a"
Issue2890EnumB Issue2890Enum = "b"
)

type Issue2890Req struct {
g.Meta `path:"/issue2890" method:"post"`
Id int
Enums Issue2890Enum `v:"required|enums"`
}

type Issue2890Res struct{}
type Issue2890Controller struct{}

func (c *Issue2890Controller) Post(ctx context.Context, req *Issue2890Req) (res *Issue2890Res, err error) {
g.RequestFromCtx(ctx).Response.Write(req.Enums)
return
}

// https://github.com/gogf/gf/issues/2890
func Test_Issue2890(t *testing.T) {
gtest.C(t, func(t *gtest.T) {

oldEnumsJson, err := gtag.GetGlobalEnums()
t.AssertNil(err)
defer t.AssertNil(gtag.SetGlobalEnums(oldEnumsJson))

err = gtag.SetGlobalEnums(`{"github.com/gogf/gf/v2/net/ghttp_test.Issue2890Enum": ["a","b"]}`)
t.AssertNil(err)

s := g.Server(guid.S())
s.Group("/api/v2", func(group *ghttp.RouterGroup) {
group.Middleware(ghttp.MiddlewareHandlerResponse)
group.Bind(Issue2890Controller{})
})
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()
time.Sleep(1000 * time.Millisecond)

c := g.Client()
c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
t.Assert(
c.PostContent(ctx, "/api/v2/issue2890", ``),
`{"code":51,"message":"The Enums field is required","data":null}`,
)
t.Assert(
c.PostContent(ctx, "/api/v2/issue2890", `{"Enums":"c"}`),
"{\"code\":51,\"message\":\"The Enums value `c` should be in enums of: [\\\"a\\\",\\\"b\\\"]\",\"data\":null}")
})
}
3 changes: 2 additions & 1 deletion util/gvalid/gvalid.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ type fieldRule struct {
Name string // Alias name for the field.
Rule string // Rule string like: "max:6"
IsMeta bool // Is this rule is from gmeta.Meta, which marks it as whole struct rule.
FieldKind reflect.Kind // Kind of struct field, which is used for parameter type checks.
FieldKind reflect.Kind // Original kind of struct field, which is used for parameter type checks.
FieldType reflect.Type // Type of struct field, which is used for parameter type checks.
}

// iNoValidation is an interface that marks current struct not validated by package `gvalid`.
Expand Down
3 changes: 3 additions & 0 deletions util/gvalid/gvalid_register.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ type RuleFuncInput struct {
// Field specifies the field for this rule to validate.
Field string

// ValueType specifies the type of the value, which might be nil.
ValueType reflect.Type

// Value specifies the value for this rule to validate.
Value *gvar.Var

Expand Down
13 changes: 7 additions & 6 deletions util/gvalid/gvalid_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,13 @@ func (v *Validator) Run(ctx context.Context) Error {
}

return v.doCheckValue(ctx, doCheckValueInput{
Name: "",
Value: v.data,
Rule: gconv.String(v.rules),
Messages: v.messages,
DataRaw: v.assoc,
DataMap: gconv.Map(v.assoc),
Name: "",
Value: v.data,
ValueType: reflect.TypeOf(v.data),
Rule: gconv.String(v.rules),
Messages: v.messages,
DataRaw: v.assoc,
DataMap: gconv.Map(v.assoc),
})
}

Expand Down
13 changes: 7 additions & 6 deletions util/gvalid/gvalid_validator_check_map.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,12 +128,13 @@ func (v *Validator) doCheckMap(ctx context.Context, params interface{}) Error {
}
// It checks each rule and its value in loop.
if validatedError := v.doCheckValue(ctx, doCheckValueInput{
Name: checkRuleItem.Name,
Value: value,
Rule: checkRuleItem.Rule,
Messages: customMessage[checkRuleItem.Name],
DataRaw: params,
DataMap: inputParamMap,
Name: checkRuleItem.Name,
Value: value,
ValueType: reflect.TypeOf(value),
Rule: checkRuleItem.Rule,
Messages: customMessage[checkRuleItem.Name],
DataRaw: params,
DataMap: inputParamMap,
}); validatedError != nil {
_, errorItem := validatedError.FirstItem()
// ===========================================================
Expand Down
16 changes: 9 additions & 7 deletions util/gvalid/gvalid_validator_check_struct.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func (v *Validator) doCheckStruct(ctx context.Context, object interface{}) Error
checkRules = make([]fieldRule, 0)
nameToRuleMap = make(map[string]string) // just for internally searching index purpose.
customMessage = make(CustomMsg) // Custom rule error message map.
checkValueData = v.assoc // Ready to be validated data, which can be type of .
checkValueData = v.assoc // Ready to be validated data.
)
if checkValueData == nil {
checkValueData = object
Expand Down Expand Up @@ -181,6 +181,7 @@ func (v *Validator) doCheckStruct(ctx context.Context, object interface{}) Error
Rule: rule,
IsMeta: isMeta,
FieldKind: field.OriginalKind(),
FieldType: field.Type(),
})
}
} else {
Expand Down Expand Up @@ -302,12 +303,13 @@ func (v *Validator) doCheckStruct(ctx context.Context, object interface{}) Error
}
// It checks each rule and its value in loop.
if validatedError := v.doCheckValue(ctx, doCheckValueInput{
Name: checkRuleItem.Name,
Value: value,
Rule: checkRuleItem.Rule,
Messages: customMessage[checkRuleItem.Name],
DataRaw: checkValueData,
DataMap: inputParamMap,
Name: checkRuleItem.Name,
Value: value,
ValueType: checkRuleItem.FieldType,
Rule: checkRuleItem.Rule,
Messages: customMessage[checkRuleItem.Name],
DataRaw: checkValueData,
DataMap: inputParamMap,
}); validatedError != nil {
_, errorItem := validatedError.FirstItem()
// ============================================================
Expand Down
25 changes: 14 additions & 11 deletions util/gvalid/gvalid_validator_check_value.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,13 @@ import (
)

type doCheckValueInput struct {
Name string // Name specifies the name of parameter `value`.
Value interface{} // Value specifies the value for the rules to be validated.
Rule string // Rule specifies the validation rules string, like "required", "required|between:1,100", etc.
Messages interface{} // Messages specifies the custom error messages for this rule from parameters input, which is usually type of map/slice.
DataRaw interface{} // DataRaw specifies the `raw data` which is passed to the Validator. It might be type of map/struct or a nil value.
DataMap map[string]interface{} // DataMap specifies the map that is converted from `dataRaw`. It is usually used internally
Name string // Name specifies the name of parameter `value`.
Value interface{} // Value specifies the value for the rules to be validated.
ValueType reflect.Type // ValueType specifies the type of the value, mainly used for value type id retrieving.
Rule string // Rule specifies the validation rules string, like "required", "required|between:1,100", etc.
Messages interface{} // Messages specifies the custom error messages for this rule from parameters input, which is usually type of map/slice.
DataRaw interface{} // DataRaw specifies the `raw data` which is passed to the Validator. It might be type of map/struct or a nil value.
DataMap map[string]interface{} // DataMap specifies the map that is converted from `dataRaw`. It is usually used internally
}

// doCheckValue does the really rules validation for single key-value.
Expand Down Expand Up @@ -146,11 +147,12 @@ func (v *Validator) doCheckValue(ctx context.Context, in doCheckValueInput) Erro
// Custom validation rules.
case customRuleFunc != nil:
err = customRuleFunc(ctx, RuleFuncInput{
Rule: ruleItems[index],
Message: message,
Field: in.Name,
Value: gvar.New(value),
Data: gvar.New(in.DataRaw),
Rule: ruleItems[index],
Message: message,
Field: in.Name,
ValueType: in.ValueType,
Value: gvar.New(value),
Data: gvar.New(in.DataRaw),
})

// Builtin validation rules.
Expand All @@ -159,6 +161,7 @@ func (v *Validator) doCheckValue(ctx context.Context, in doCheckValueInput) Erro
RuleKey: ruleKey,
RulePattern: rulePattern,
Field: in.Name,
ValueType: in.ValueType,
Value: gvar.New(value),
Data: gvar.New(in.DataRaw),
Message: message,
Expand Down
17 changes: 10 additions & 7 deletions util/gvalid/internal/builtin/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
package builtin

import (
"reflect"

"github.com/gogf/gf/v2/container/gvar"
)

Expand All @@ -26,13 +28,14 @@ type Rule interface {
}

type RunInput struct {
RuleKey string // RuleKey is like the "max" in rule "max: 6"
RulePattern string // RulePattern is like "6" in rule:"max:6"
Field string // The field name of Value.
Value *gvar.Var // Value specifies the value for this rule to validate.
Data *gvar.Var // Data specifies the `data` which is passed to the Validator.
Message string // Message specifies the custom error message or configured i18n message for this rule.
Option RunOption // Option provides extra configuration for validation rule.
RuleKey string // RuleKey is like the "max" in rule "max: 6"
RulePattern string // RulePattern is like "6" in rule:"max:6"
Field string // The field name of Value.
ValueType reflect.Type // ValueType specifies the type of the value, which might be nil.
Value *gvar.Var // Value specifies the value for this rule to validate.
Data *gvar.Var // Data specifies the `data` which is passed to the Validator.
Message string // Message specifies the custom error message or configured i18n message for this rule.
Option RunOption // Option provides extra configuration for validation rule.
}

type RunOption struct {
Expand Down
66 changes: 33 additions & 33 deletions util/gvalid/internal/builtin/builtin_enums.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,10 @@ package builtin
import (
"errors"
"fmt"
"reflect"

"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/json"
"github.com/gogf/gf/v2/internal/reflection"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/util/gtag"
Expand All @@ -39,40 +37,42 @@ func (r RuleEnums) Message() string {
}

func (r RuleEnums) Run(in RunInput) error {
originTypeAndKind := reflection.OriginTypeAndKind(in.Data.Val())
switch originTypeAndKind.OriginKind {
case reflect.Struct:
for i := 0; i < originTypeAndKind.OriginType.NumField(); i++ {
field := originTypeAndKind.OriginType.Field(i)
if in.Field == field.Name {
var (
typeId = fmt.Sprintf(`%s.%s`, field.Type.PkgPath(), field.Type.Name())
tagEnums = gtag.GetEnumsByType(typeId)
)
if tagEnums == "" {
return gerror.NewCodef(
gcode.CodeInvalidOperation,
`no enums found for type "%s", missing using command "gf gen enums"?`,
typeId,
)
}
var enumsValues = make([]interface{}, 0)
if err := json.Unmarshal([]byte(tagEnums), &enumsValues); err != nil {
return err
}
if !gstr.InArray(gconv.Strings(enumsValues), in.Value.String()) {
return errors.New(gstr.Replace(
in.Message, `{enums}`, tagEnums,
))
}
}
}

default:
if in.ValueType == nil {
return gerror.NewCode(
gcode.CodeInvalidParameter,
`value type cannot be empty to use validation rule "enums"`,
)
}
var (
pkgPath = in.ValueType.PkgPath()
typeName = in.ValueType.Name()
)
if pkgPath == "" {
return gerror.NewCodef(
gcode.CodeInvalidOperation,
`no pkg path found for type "%s"`,
in.ValueType.String(),
)
}
var (
typeId = fmt.Sprintf(`%s.%s`, pkgPath, typeName)
tagEnums = gtag.GetEnumsByType(typeId)
)
if tagEnums == "" {
return gerror.NewCodef(
gcode.CodeInvalidOperation,
`"enums" validation rule can only be used in struct validation currently`,
`no enums found for type "%s", missing using command "gf gen enums"?`,
typeId,
)
}
var enumsValues = make([]interface{}, 0)
if err := json.Unmarshal([]byte(tagEnums), &enumsValues); err != nil {
return err
}
if !gstr.InArray(gconv.Strings(enumsValues), in.Value.String()) {
return errors.New(gstr.Replace(
in.Message, `{enums}`, tagEnums,
))
}
return nil
}

0 comments on commit 8d925d4

Please sign in to comment.