def (#12)
All checks were successful
Go Action / goaction (push) Successful in 29s

Reviewed-on: #12
This commit was merged in pull request #12.
This commit is contained in:
2025-12-26 14:55:42 +03:00
parent 22dacb741f
commit f9a0411192
129 changed files with 4660 additions and 1456 deletions

19
validator/enum.go Normal file
View File

@@ -0,0 +1,19 @@
package validator
import (
"slices"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/param"
)
func Enum(enum ...string) Validator {
return func(_ param.Params, in config.Value) error {
val := in.String()
if slices.Contains(enum, val) {
return nil
}
return NewError(ErrInvalid, val, enum)
}
}

30
validator/enum_test.go Normal file
View File

@@ -0,0 +1,30 @@
package validator_test
import (
"errors"
"testing"
"gitoa.ru/go-4devs/config/definition/option"
"gitoa.ru/go-4devs/config/validator"
"gitoa.ru/go-4devs/config/value"
)
func TestEnum(t *testing.T) {
t.Parallel()
vars := option.String("test", "test")
validValue := value.New("valid")
invalidValue := value.New("invalid")
enum := validator.Enum("valid", "other", "three")
err := enum(vars, validValue)
if err != nil {
t.Errorf("expected valid value got err:%s", err)
}
iErr := enum(vars, invalidValue)
if !errors.Is(iErr, validator.ErrInvalid) {
t.Errorf("expected err:%s, got: %s", validator.ErrInvalid, iErr)
}
}

37
validator/error.go Normal file
View File

@@ -0,0 +1,37 @@
package validator
import (
"errors"
"fmt"
)
var (
ErrInvalid = errors.New("invalid value")
ErrNotBlank = errors.New("not blank")
)
func NewError(err error, value, expect any) Error {
return Error{
err: err,
value: value,
expect: expect,
}
}
type Error struct {
err error
value any
expect any
}
func (e Error) Error() string {
return fmt.Sprintf("%s: expext: %s, given: %s", e.err, e.expect, e.value)
}
func (e Error) Is(err error) bool {
return errors.Is(e.err, err)
}
func (e Error) Unwrap() error {
return e.err
}

84
validator/not_blank.go Normal file
View File

@@ -0,0 +1,84 @@
package validator
import (
"fmt"
"time"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/definition/option"
"gitoa.ru/go-4devs/config/param"
)
func NotBlank(fn param.Params, in config.Value) error {
dataType := param.Type(fn)
if option.IsSlice(fn) {
return sliceByType(dataType, in)
}
if notBlank(dataType, in) {
return nil
}
return ErrNotBlank
}
func sliceByType(vType any, in config.Value) error {
switch vType.(type) {
case string:
return sliceBy[string](in)
case int:
return sliceBy[int](in)
case int64:
return sliceBy[int64](in)
case uint:
return sliceBy[uint](in)
case uint64:
return sliceBy[uint64](in)
case float64:
return sliceBy[float64](in)
case bool:
return sliceBy[bool](in)
case time.Duration:
return sliceBy[time.Duration](in)
case time.Time:
return sliceBy[time.Time](in)
default:
return sliceBy[any](in)
}
}
func sliceBy[T any](in config.Value) error {
var data []T
if err := in.Unmarshal(&data); err != nil {
return fmt.Errorf("%w:%w", ErrNotBlank, err)
}
if len(data) > 0 {
return nil
}
return fmt.Errorf("%w", ErrNotBlank)
}
func notBlank(vType any, in config.Value) bool {
switch vType.(type) {
case int:
return in.Int() != 0
case int64:
return in.Int64() != 0
case uint:
return in.Uint() != 0
case uint64:
return in.Uint64() != 0
case float64:
return in.Float64() != 0
case time.Duration:
return in.Duration() != 0
case time.Time:
return !in.Time().IsZero()
case string:
return len(in.String()) > 0
default:
return in.Any() != nil
}
}

128
validator/not_blank_test.go Normal file
View File

@@ -0,0 +1,128 @@
package validator_test
import (
"errors"
"testing"
"time"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/definition/option"
"gitoa.ru/go-4devs/config/validator"
"gitoa.ru/go-4devs/config/value"
)
func TestNotBlank(t *testing.T) {
t.Parallel()
for name, ca := range casesNotBlank() {
valid := validator.NotBlank
if err := valid(ca.vars, ca.value); err != nil {
t.Errorf("case: %s, expected error <nil>, got: %s", name, err)
}
if ca.empty == nil {
ca.empty = value.EmptyValue()
}
emptErr := valid(ca.vars, ca.empty)
if emptErr == nil || !errors.Is(emptErr, validator.ErrNotBlank) {
t.Errorf("case empty: %s, expect: %s, got:%v", name, validator.ErrNotBlank, emptErr)
}
}
}
type cases struct {
vars config.Option
value config.Value
empty config.Value
}
func casesNotBlank() map[string]cases {
return map[string]cases{
"any": {
vars: option.New("any", "any", nil),
value: value.New(float32(1)),
},
"array int": {
vars: option.Int("int", "array int", option.Slice),
value: value.New([]int{1}),
empty: value.New([]int{}),
},
"array int64": {
vars: option.Int64("int64", "array int64", option.Slice),
value: value.New([]int64{1}),
empty: value.New([]int64{}),
},
"array uint": {
vars: option.Uint("uint", "array uint", option.Slice),
value: value.New([]uint{1}),
empty: value.New([]uint{}),
},
"array uint64": {
vars: option.Uint64("uint64", "array uint64", option.Slice),
value: value.New([]uint64{1}),
empty: value.New([]uint64{}),
},
"array float64": {
vars: option.Float64("float64", "array float64", option.Slice),
value: value.New([]float64{0.2}),
empty: value.New([]float64{}),
},
"array bool": {
vars: option.Bool("bool", "array bool", option.Slice),
value: value.New([]bool{true, false}),
empty: value.New([]bool{}),
},
"array duration": {
vars: option.Duration("duration", "array duration", option.Slice),
value: value.New([]time.Duration{time.Second}),
empty: value.New([]time.Duration{}),
},
"array time": {
vars: option.Time("time", "array time", option.Slice),
value: value.New([]time.Time{time.Now()}),
empty: value.New([]time.Time{}),
},
"array string": {
vars: option.String("string", "array string", option.Slice),
value: value.New([]string{"value"}),
empty: value.New([]string{}),
},
"int": {
vars: option.Int("int", "int"),
value: value.New(int(1)),
},
"int64": {
vars: option.Int64("int64", "int64"),
value: value.New(int64(2)),
},
"uint": {
vars: option.Uint("uint", "uint"),
value: value.New(uint(1)),
empty: value.New([]uint{1}),
},
"uint64": {
vars: option.Uint64("uint64", "uint64"),
value: value.New(uint64(10)),
},
"float64": {
vars: option.Float64("float64", "float64"),
value: value.New(float64(.00001)),
},
"duration": {
vars: option.Duration("duration", "duration"),
value: value.New(time.Minute),
empty: value.New("same string"),
},
"time": {
vars: option.Time("time", "time"),
value: value.New(time.Now()),
},
"string": {
vars: option.String("string", "string"),
value: value.New("string"),
empty: value.New(""),
},
}
}

49
validator/valid.go Normal file
View File

@@ -0,0 +1,49 @@
package validator
import (
"fmt"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/param"
)
func Chain(v ...Validator) Validator {
return func(vr param.Params, in config.Value) error {
for _, valid := range v {
err := valid(vr, in)
if err != nil {
return err
}
}
return nil
}
}
const paramValid = "param.valid"
type Validator func(param.Params, config.Value) error
func Valid(in ...Validator) param.Option {
return func(v param.Params) param.Params {
return param.With(v, paramValid, in)
}
}
func Validate(key []string, fn param.Params, in config.Value) error {
params, ok := fn.Param(paramValid)
if !ok {
return nil
}
valids, _ := params.([]Validator)
for _, valid := range valids {
err := valid(fn, in)
if err != nil {
return fmt.Errorf("%s:%w", key, err)
}
}
return nil
}

33
validator/valid_test.go Normal file
View File

@@ -0,0 +1,33 @@
package validator_test
import (
"errors"
"testing"
"gitoa.ru/go-4devs/config/definition/option"
"gitoa.ru/go-4devs/config/validator"
"gitoa.ru/go-4devs/config/value"
)
func TestChain(t *testing.T) {
t.Parallel()
vars := option.String("test", "test")
validValue := value.New("one")
invalidValue := value.New([]string{"one"})
valid := validator.Chain(
validator.NotBlank,
validator.Enum("one", "two"),
)
err := valid(vars, validValue)
if err != nil {
t.Errorf("expected valid value, got: %s", err)
}
ierr := valid(vars, invalidValue)
if !errors.Is(ierr, validator.ErrNotBlank) {
t.Errorf("expected not blank, got:%s", ierr)
}
}