Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitaly.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'internal/validator/validator_test.go')
-rw-r--r--internal/validator/validator_test.go210
1 files changed, 210 insertions, 0 deletions
diff --git a/internal/validator/validator_test.go b/internal/validator/validator_test.go
new file mode 100644
index 000000000..b4ea7cfca
--- /dev/null
+++ b/internal/validator/validator_test.go
@@ -0,0 +1,210 @@
+package validator_test
+
+import (
+ "errors"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+ "gitlab.com/gitlab-org/gitaly/v15/internal/validator"
+)
+
+type CustomString string
+
+// Custom types can have validators attached to them. Their path in the
+// struct's hierarchy is automatically annotated on the errors.
+func (str CustomString) Validate() error {
+ // The validator functions are composable.
+ return validator.Combine(
+ validator.IsSet[CustomString],
+ validator.Equal[CustomString]("success"),
+ )(str)
+}
+
+type StringEnum string
+
+func (enum StringEnum) Validate() error {
+ return validator.IsOneOf("good-1", "good-2", "good-3")(string(enum))
+}
+
+type ChildStruct struct {
+ ShouldFail bool
+ EnumValue StringEnum `toml:"enum_value"`
+ CustomString CustomString `toml:"custom_string"`
+ // Fields which don't have validation logic are ignored.
+ UnvalidatedString string `toml:"unvalidated_string"`
+}
+
+// Validate functons are called on field that implements one, even in subfields.
+func (c ChildStruct) Validate() error {
+ if c.ShouldFail {
+ // Returning a validator.Error allows for annotating the returned error with a
+ // key so the correct location in the struct can be reported even if a subfield
+ // was being validated from a parent struct level validator.
+ return validator.NewError("should_fail", errors.New("struct validation error"))
+ }
+
+ return nil
+}
+
+type StringSlice []CustomString
+
+func (slc StringSlice) Validate() error {
+ if len(slc) == 0 {
+ return errors.New("must have elements")
+ }
+
+ return nil
+}
+
+type Configuration struct {
+ ShouldFail bool
+ StringSlice StringSlice `toml:"string_slice"`
+ CustomString CustomString `toml:"custom_string"`
+ // The ChildStruct gets walked into as well and validation invoked for each
+ // field separately.
+ ChildStruct ChildStruct `toml:"child_struct"`
+ ValidatedInt validator.Field[int] `toml:"validated_int"`
+ ValidatedStruct validator.Field[ChildStruct] `toml:"validated_struct"`
+}
+
+// Validate functons are called on field that implements one, even on the root level.
+func (c Configuration) Validate() error {
+ if c.ShouldFail {
+ // Returned errors don't need to have a special type.
+ return errors.New("struct validation error")
+ }
+
+ return nil
+}
+
+func newChildStruct() ChildStruct {
+ return ChildStruct{
+ EnumValue: "good-1",
+ CustomString: "success",
+ UnvalidatedString: "default_unvalidated",
+ }
+}
+
+func NewConfiguration() Configuration {
+ return Configuration{
+ CustomString: "success",
+ StringSlice: StringSlice{"success", "success", "success"},
+ ChildStruct: newChildStruct(),
+ ValidatedInt: validator.NewField(12, validator.IsInRange(10, 15)),
+ ValidatedStruct: validator.NewField(newChildStruct(), func(value ChildStruct) error {
+ if value != newChildStruct() {
+ return errors.New("struct must not be changed")
+ }
+
+ return nil
+ }),
+ }
+}
+
+func TestValidate(t *testing.T) {
+ t.Parallel()
+
+ for _, tc := range []struct {
+ desc string
+ cfg func(cfg *Configuration)
+ expectedErr string
+ }{
+ {
+ desc: "valid configuration",
+ cfg: func(*Configuration) {},
+ },
+ {
+ desc: "invalid top level value",
+ cfg: func(cfg *Configuration) {
+ cfg.CustomString = ""
+ },
+ expectedErr: "custom_string: must be set",
+ },
+ {
+ desc: "invalid child value",
+ cfg: func(cfg *Configuration) {
+ cfg.ChildStruct.CustomString = ""
+ },
+ expectedErr: "child_struct.custom_string: must be set",
+ },
+ {
+ desc: "multiple invalid",
+ cfg: func(cfg *Configuration) {
+ cfg.CustomString = ""
+ cfg.ChildStruct.CustomString = "fail"
+ },
+ expectedErr: `custom_string: must be set
+child_struct.custom_string: must equal "success"`,
+ },
+ {
+ desc: "field not in range",
+ cfg: func(cfg *Configuration) {
+ cfg.ValidatedInt.Value = 2
+ },
+ expectedErr: "validated_int: must be in range [10, 15]",
+ },
+ {
+ desc: "value equals min of the range",
+ cfg: func(cfg *Configuration) {
+ cfg.ValidatedInt.Value = 10
+ },
+ },
+ {
+ desc: "value equals max of the range",
+ cfg: func(cfg *Configuration) {
+ cfg.ValidatedInt.Value = 15
+ },
+ },
+ {
+ desc: "struct validation",
+ cfg: func(cfg *Configuration) {
+ cfg.ShouldFail = true
+ },
+ expectedErr: "struct validation error",
+ },
+ {
+ desc: "child struct validation",
+ cfg: func(cfg *Configuration) {
+ cfg.ChildStruct.ShouldFail = true
+ },
+ expectedErr: "child_struct.should_fail: struct validation error",
+ },
+ {
+ desc: "field struct validation",
+ cfg: func(cfg *Configuration) {
+ cfg.ValidatedStruct.Value.EnumValue = "bad-1"
+ },
+ expectedErr: `validated_struct: struct must not be changed
+validated_struct.enum_value: must be one of [good-1 good-2 good-3]`,
+ },
+ {
+ desc: "slice validation",
+ cfg: func(cfg *Configuration) {
+ cfg.StringSlice = nil
+ },
+ expectedErr: `string_slice: must have elements`,
+ },
+ {
+ desc: "invalid elements in slice",
+ cfg: func(cfg *Configuration) {
+ cfg.StringSlice = StringSlice{"", "success", "fail"}
+ },
+ expectedErr: `string_slice.0: must be set
+string_slice.2: must equal "success"`,
+ },
+ } {
+ tc := tc
+ t.Run(tc.desc, func(t *testing.T) {
+ t.Parallel()
+
+ cfg := NewConfiguration()
+ tc.cfg(&cfg)
+
+ if err := validator.Validate(cfg); tc.expectedErr == "" {
+ require.NoError(t, err)
+ } else {
+ require.EqualError(t, err, tc.expectedErr)
+ }
+ })
+ }
+}