From 2ece368810588d6d11504b71a783f9a617eab3f4 Mon Sep 17 00:00:00 2001 From: John Guo Date: Wed, 7 Jun 2023 09:48:29 +0800 Subject: [PATCH] add new validation rule "enums" (#2690) --- util/gtag/gtag_enums.go | 9 +++ util/gtag/gtag_z_unit_test.go | 20 +++++ .../gvalid/gvalid_z_unit_feature_rule_test.go | 34 ++++++++ util/gvalid/internal/builtin/builtin_enums.go | 78 +++++++++++++++++++ 4 files changed, 141 insertions(+) create mode 100644 util/gvalid/internal/builtin/builtin_enums.go diff --git a/util/gtag/gtag_enums.go b/util/gtag/gtag_enums.go index 3ab37462d..2257dfa9f 100644 --- a/util/gtag/gtag_enums.go +++ b/util/gtag/gtag_enums.go @@ -21,6 +21,15 @@ func SetGlobalEnums(enumsJson string) error { return json.Unmarshal([]byte(enumsJson), &enumsMap) } +// GetGlobalEnums retrieves and returns the global enums. +func GetGlobalEnums() (string, error) { + enumsByes, err := json.Marshal(enumsMap) + if err != nil { + return "", err + } + return string(enumsByes), nil +} + // GetEnumsByType retrieves and returns the stored enums json by type name. // The type name is like: github.com/gogf/gf/v2/encoding/gjson.ContentType func GetEnumsByType(typeName string) string { diff --git a/util/gtag/gtag_z_unit_test.go b/util/gtag/gtag_z_unit_test.go index a8e8160c4..4460ac95c 100644 --- a/util/gtag/gtag_z_unit_test.go +++ b/util/gtag/gtag_z_unit_test.go @@ -134,3 +134,23 @@ func Test_Parse(t *testing.T) { t.Assert(gtag.Parse(content), expect) }) } + +func Test_SetGlobalEnums(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + oldEnumsJson, err := gtag.GetGlobalEnums() + t.AssertNil(err) + + err = gtag.SetGlobalEnums(`{"k8s.io/apimachinery/pkg/api/resource.Format": [ + "BinarySI", + "DecimalExponent", + "DecimalSI" + ]}`) + t.AssertNil(err) + t.Assert(gtag.GetEnumsByType("k8s.io/apimachinery/pkg/api/resource.Format"), `[ + "BinarySI", + "DecimalExponent", + "DecimalSI" + ]`) + t.AssertNil(gtag.SetGlobalEnums(oldEnumsJson)) + }) +} diff --git a/util/gvalid/gvalid_z_unit_feature_rule_test.go b/util/gvalid/gvalid_z_unit_feature_rule_test.go index 078b380bc..71b104453 100755 --- a/util/gvalid/gvalid_z_unit_feature_rule_test.go +++ b/util/gvalid/gvalid_z_unit_feature_rule_test.go @@ -16,6 +16,7 @@ import ( "github.com/gogf/gf/v2/os/gctx" "github.com/gogf/gf/v2/os/gtime" "github.com/gogf/gf/v2/test/gtest" + "github.com/gogf/gf/v2/util/gtag" ) var ( @@ -1550,3 +1551,36 @@ func Test_LTE(t *testing.T) { t.AssertNil(err) }) } + +func Test_Enums(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + type EnumsTest string + const ( + EnumsTestA EnumsTest = "a" + EnumsTestB EnumsTest = "b" + ) + type Params struct { + Id int + Enums EnumsTest `v:"enums"` + } + + oldEnumsJson, err := gtag.GetGlobalEnums() + t.AssertNil(err) + defer t.AssertNil(gtag.SetGlobalEnums(oldEnumsJson)) + + err = gtag.SetGlobalEnums(`{"github.com/gogf/gf/v2/util/gvalid_test.EnumsTest": ["a","b"]}`) + t.AssertNil(err) + + err = g.Validator().Data(&Params{ + Id: 1, + Enums: EnumsTestB, + }).Run(ctx) + t.AssertNil(err) + + err = g.Validator().Data(&Params{ + Id: 1, + Enums: "c", + }).Run(ctx) + t.Assert(err, "The Enums value `c` should be in enums of: [\"a\",\"b\"]") + }) +} diff --git a/util/gvalid/internal/builtin/builtin_enums.go b/util/gvalid/internal/builtin/builtin_enums.go new file mode 100644 index 000000000..b8a900bdc --- /dev/null +++ b/util/gvalid/internal/builtin/builtin_enums.go @@ -0,0 +1,78 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +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" +) + +// RuleEnums implements `enums` rule: +// Value should be in enums of its constant type. +// +// Format: enums +type RuleEnums struct{} + +func init() { + Register(RuleEnums{}) +} + +func (r RuleEnums) Name() string { + return "enums" +} + +func (r RuleEnums) Message() string { + return "The {field} value `{value}` should be in enums of: {enums}" +} + +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"`, + 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: + return gerror.NewCode( + gcode.CodeInvalidOperation, + `"enums" validation rule can only be used in struct validation currently`, + ) + } + return nil +}