From ff27adbde5bbdb166a99c9c60e6ac5df3c2232dd Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Thu, 22 Aug 2024 06:59:07 -0700 Subject: [PATCH] Config rules. --- checker2.go | 117 +++++++++++++++++++++++++++++++++++++++++++++++ checker2_test.go | 41 +++++++++++++++++ 2 files changed, 158 insertions(+) create mode 100644 checker2.go create mode 100644 checker2_test.go diff --git a/checker2.go b/checker2.go new file mode 100644 index 0000000..b20a6a4 --- /dev/null +++ b/checker2.go @@ -0,0 +1,117 @@ +package checker + +import ( + "fmt" + "reflect" + "strings" +) + +type Rule struct { + FieldIndex []int + Checkers []CheckFunc +} + +// rulesRegistry stores a collection of rules associated with a specific type. +var rulesRegistry = map[reflect.Type][]*Rule{} + +// checkersCache stores checkers associated with a specific config. +var checkersCache = map[string]CheckFunc{} + +// RegisterRulesFromConfig registers rules based on the provided struct type and config map. +func RegisterRulesFromConfig(structType reflect.Type, nameToConfig map[string]string) error { + rules := make([]*Rule, len(nameToConfig)) + + for name, config := range nameToConfig { + field, ok := structType.FieldByName(name) + if !ok { + return fmt.Errorf("field %s not found", name) + } + + checkers, err := initCheckersFromConfig(config) + if err != nil { + return fmt.Errorf("field %s has errors: %w", name, err) + } + + rules = append(rules, &Rule{ + FieldIndex: field.Index, + Checkers: checkers, + }) + } + + rulesRegistry[structType] = rules + + return nil +} + +// RegisterRulesFromTag registers rules based on the provided struct type and field tags. +func RegisterRulesFromTag(structType reflect.Type) error { + structTypes := []reflect.Type{structType} + + for len(structTypes) > 0 { + structType := structTypes[0] + structTypes = structTypes[1:] + + // Skip already registed structs + _, found := rulesRegistry[structType] + if found { + continue + } + + var rules []*Rule + + for i := range structType.NumField() { + field := structType.Field(i) + + // Queue the nested structs + if field.Type.Kind() == reflect.Struct { + structTypes = append(structTypes, field.Type) + continue + } + + config := field.Tag.Get("checkers") + if config == "" { + continue + } + + checkers, err := initCheckersFromConfig(config) + if err != nil { + return fmt.Errorf("field %s has errors: %w", field.Name, err) + } + + rules = append(rules, &Rule{ + FieldIndex: field.Index, + Checkers: checkers, + }) + } + + rulesRegistry[structType] = rules + } + + return nil +} + +// initCheckersFromConfig parses the given config string and initializes checker instances. +func initCheckersFromConfig(config string) ([]CheckFunc, error) { + items := strings.Fields(config) + checkers := make([]CheckFunc, len(items)) + + for i, item := range items { + checker, ok := checkersCache[item] + if ok { + checkers[i] = checker + continue + } + + name, params, _ := strings.Cut(item, ":") + + maker, ok := makers[name] + if !ok { + return nil, fmt.Errorf("checker %s not found", name) + } + + checkers[i] = maker(params) + checkersCache[item] = checkers[i] + } + + return checkers, nil +} diff --git a/checker2_test.go b/checker2_test.go new file mode 100644 index 0000000..5e8ffcc --- /dev/null +++ b/checker2_test.go @@ -0,0 +1,41 @@ +package checker_test + +import ( + "reflect" + "testing" + + "github.com/cinar/checker" +) + +func TestRegisterRulesFromConfig(t *testing.T) { + type Person struct { + Name string + Email string + } + + err := checker.RegisterRulesFromConfig(reflect.TypeFor[Person](), map[string]string{ + "Name": "trim required", + "Email": "required email", + }) + if err != nil { + t.Fatal(err) + } +} + +func TestRegisterRulesFromTag(t *testing.T) { + type Address struct { + Street string `checkers:"required"` + } + + type Person struct { + Name string `checkers:"trim required"` + Email string `checkers:"required email"` + Home Address + Work Address + } + + err := checker.RegisterRulesFromTag(reflect.TypeFor[Person]()) + if err != nil { + t.Fatal(err) + } +}