Skip to content

Commit

Permalink
merged typedvalidator into standard validators
Browse files Browse the repository at this point in the history
  • Loading branch information
s12chung committed Oct 8, 2023
1 parent 031ecee commit c30e906
Show file tree
Hide file tree
Showing 18 changed files with 298 additions and 527 deletions.
4 changes: 2 additions & 2 deletions db/pkg/cli/search/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,10 @@ type Query struct {
}

func init() {
firm.MustRegisterType(firm.NewDefinition(Config{}).Validates(firm.RuleMap{
firm.MustRegisterType(firm.DefaultRegistry, firm.NewDefinition[Config]().Validates(firm.RuleMap{
"Queries": {},
}))
firm.MustRegisterType(firm.NewDefinition(Query{}).Validates(firm.RuleMap{
firm.MustRegisterType(firm.DefaultRegistry, firm.NewDefinition[Query]().Validates(firm.RuleMap{
"Str": {rule.Present{}},
}))
}
Expand Down
2 changes: 1 addition & 1 deletion db/pkg/db/note.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
)

func init() {
firm.MustRegisterType(firm.NewDefinition(NoteCreateParams{}).Validates(firm.RuleMap{
firm.MustRegisterType(firm.DefaultRegistry, firm.NewDefinition[NoteCreateParams]().Validates(firm.RuleMap{
"Text": {rule.Present{}},
"Translation": {rule.Present{}},
"Explanation": {rule.Present{}},
Expand Down
2 changes: 1 addition & 1 deletion pkg/api/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ type LocalStoreConfig struct {
}

func init() {
firm.MustRegisterType(firm.NewDefinition(LocalStoreConfig{}).Validates(firm.RuleMap{
firm.MustRegisterType(firm.DefaultRegistry, firm.NewDefinition[LocalStoreConfig]().Validates(firm.RuleMap{
"Origin": {rule.Present{}},
"KeyBasePath": {rule.Present{}},
"EncryptorPath": {rule.Present{}},
Expand Down
4 changes: 2 additions & 2 deletions pkg/api/parts.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ type PartCreateMultiRequestPart struct {
}

func init() {
firm.MustRegisterType(firm.NewDefinition(PartCreateMultiRequest{}).Validates(firm.RuleMap{
firm.MustRegisterType(firm.DefaultRegistry, firm.NewDefinition[PartCreateMultiRequest]().Validates(firm.RuleMap{
"Parts": {
rule.Present{},
rule.Attr{Of: attr.Len{}, Rule: rule.Less[int]{OrEqual: true, To: 20}},
Expand Down Expand Up @@ -63,7 +63,7 @@ func (rs Routes) PartCreateMulti(r *http.Request, txQs db.TxQs) (any, *jhttp.HTT
type PartCreateOrUpdateRequest PartCreateMultiRequestPart

func init() {
firm.MustRegisterType(firm.NewDefinition(PartCreateOrUpdateRequest{}).Validates(firm.RuleMap{
firm.MustRegisterType(firm.DefaultRegistry, firm.NewDefinition[PartCreateOrUpdateRequest]().Validates(firm.RuleMap{
"Text": {rule.TrimPresent{}},
}))
}
Expand Down
6 changes: 3 additions & 3 deletions pkg/api/pre_part_lists.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ type PrePartListSignRequest struct {
}

func init() {
firm.MustRegisterType(firm.NewDefinition(PrePartListSignRequest{}).Validates(firm.RuleMap{
firm.MustRegisterType(firm.DefaultRegistry, firm.NewDefinition[PrePartListSignRequest]().Validates(firm.RuleMap{
"PreParts": {rule.Present{}},
}))
}
Expand Down Expand Up @@ -104,7 +104,7 @@ type PrePartListVerifyRequest struct {
}

func init() {
firm.MustRegisterType(firm.NewDefinition(PrePartListVerifyRequest{}).Validates(firm.RuleMap{
firm.MustRegisterType(firm.DefaultRegistry, firm.NewDefinition[PrePartListVerifyRequest]().Validates(firm.RuleMap{
"Text": {rule.Present{}},
}))
}
Expand Down Expand Up @@ -133,7 +133,7 @@ type PrePartListCreateRequest struct {
}

func init() {
firm.MustRegisterType(firm.NewDefinition(PrePartListCreateRequest{}).Validates(firm.RuleMap{
firm.MustRegisterType(firm.DefaultRegistry, firm.NewDefinition[PrePartListCreateRequest]().Validates(firm.RuleMap{
"ExtractorType": {rule.Present{}},
"Text": {rule.Present{}},
}))
Expand Down
4 changes: 2 additions & 2 deletions pkg/api/sources.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ type SourceCreateRequest struct {
}

func init() {
firm.MustRegisterType(firm.NewDefinition(SourceCreateRequest{}).Validates(firm.RuleMap{
firm.MustRegisterType(firm.DefaultRegistry, firm.NewDefinition[SourceCreateRequest]().Validates(firm.RuleMap{
"PartCreateMultiRequest": {},
}))
}
Expand Down Expand Up @@ -66,7 +66,7 @@ type SourceUpdateRequest struct {
}

func init() {
firm.MustRegisterType(firm.NewDefinition(SourceUpdateRequest{}).Validates(firm.RuleMap{
firm.MustRegisterType(firm.DefaultRegistry, firm.NewDefinition[SourceUpdateRequest]().Validates(firm.RuleMap{
"Name": {rule.Present{}},
}))
}
Expand Down
19 changes: 9 additions & 10 deletions pkg/firm/definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,25 @@ import (
)

// NewDefinition returns a new Definition
func NewDefinition(data any) *Definition {
validator := &Definition{
typ: indirectType(reflect.TypeOf(data)),
func NewDefinition[T any]() *Definition[T] {
var zero T
validator := &Definition[T]{
typ: indirectType(reflect.TypeOf(zero)),
topLevelRules: []Rule{},
ruleMap: RuleMap{},
}
return validator
}

// Definition is a definition of a validation for structs
type Definition struct {
type Definition[T any] struct {
typ reflect.Type
topLevelRules []Rule
ruleMap RuleMap
}

// ValidatesTopLevel defines rules at top level object
func (s *Definition) ValidatesTopLevel(rules ...Rule) *Definition {
func (s *Definition[T]) ValidatesTopLevel(rules ...Rule) *Definition[T] {
if len(s.topLevelRules) != 0 {
panic(fmt.Sprintf("ValidatesTopLevel() called twice in type: %v", s.typ.String()))
}
Expand All @@ -32,7 +33,7 @@ func (s *Definition) ValidatesTopLevel(rules ...Rule) *Definition {
}

// Validates defines rules for fields
func (s *Definition) Validates(ruleMap RuleMap) *Definition {
func (s *Definition[T]) Validates(ruleMap RuleMap) *Definition[T] {
if len(s.ruleMap) != 0 {
panic(fmt.Sprintf("Validates() called twice in type: %v", s.typ.String()))
}
Expand All @@ -47,11 +48,9 @@ func (s *Definition) Validates(ruleMap RuleMap) *Definition {
}

// TopLevelRules return the rules that apply to the top level
func (s *Definition) TopLevelRules() []Rule {
func (s *Definition[T]) TopLevelRules() []Rule {
return s.topLevelRules
}

// RuleMap returns the map of rules for the structure
func (s *Definition) RuleMap() RuleMap {
return s.ruleMap
}
func (s *Definition[T]) RuleMap() RuleMap { return s.ruleMap }
25 changes: 8 additions & 17 deletions pkg/firm/definition_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,24 @@ import (
"github.com/stretchr/testify/require"
)

type typedDefinition struct {
Primitive int
}

func TestTypedDefinition_ValidatesTopLevel(t *testing.T) {
func TestDefinition_ValidatesTopLevel(t *testing.T) {
require := require.New(t)
rules := []Rule{presentRule{}}

definition := NewDefinition(typedDefinition{}).ValidatesTopLevel(rules...)
definition := NewDefinition[Child]().ValidatesTopLevel(rules...)
require.Equal(rules, definition.TopLevelRules())
require.Panics(func() {
definition.ValidatesTopLevel()
})
}

func TestTypedDefinition_Validates(t *testing.T) {
func TestDefinition_Validates(t *testing.T) {
require := require.New(t)
ruleMap := RuleMap{
"Primitive": {presentRule{}},
}

definition := NewDefinition(typedDefinition{}).Validates(ruleMap)
ruleMap := RuleMap{"Validates": {presentRule{}}}
definition := NewDefinition[Child]().Validates(ruleMap)
require.Equal(ruleMap, definition.RuleMap())
require.Panics(func() {
definition.Validates(RuleMap{})
})
require.Panics(func() {
NewDefinition(typedDefinition{}).Validates(RuleMap{"DoesNotExist": {}})
})

require.Panics(func() { definition.Validates(RuleMap{}) })
require.Panics(func() { NewDefinition[Child]().Validates(RuleMap{"DoesNotExist": {}}) })
}
22 changes: 10 additions & 12 deletions pkg/firm/firm.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,10 @@ import (
"reflect"
)

type anyType struct{}
// Any is a helper that returns the type used to represent `any` in types
type Any struct{}

var anyTyp = AnyType()

// AnyType is a helper that returns the type used to fill in `nil` types
func AnyType() reflect.Type { return reflect.TypeOf(anyType{}) }

// MustRegisterType registers the TypeDefinition to the DefaultRegistry, panics if there is an error
var MustRegisterType = DefaultRegistry.MustRegisterType

// RegisterType registers the TypeDefinition to the DefaultRegistry
var RegisterType = DefaultRegistry.RegisterType
var anyTyp = reflect.TypeOf(Any{})

// Validate validates the data with the DefaultRegistry
var Validate = DefaultRegistry.Validate
Expand All @@ -25,7 +17,7 @@ var Validate = DefaultRegistry.Validate
var DefaultRegistry = &Registry{}

// DefaultValidator is the validator used by registries for not found types when DefaultValidator is not defined
var DefaultValidator = MustNewValueValidator(nil, NotFoundRule{})
var DefaultValidator = MustNewValueValidator[Any](NotFoundRule{})

// NotFoundRule is the rule used for not found types in the DefaultValidator
type NotFoundRule struct{}
Expand Down Expand Up @@ -60,3 +52,9 @@ type Validator interface {
Validate(data any) ErrorMap
ValidateMerge(value reflect.Value, key string, errorMap ErrorMap)
}

// TypedValidator is a generic firm.Validator that has a typed ValidateTyped() function
type TypedValidator[T any] interface {
Validator
ValidateTyped(data T) ErrorMap
}
6 changes: 3 additions & 3 deletions pkg/firm/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ type unregistered struct{}
var testRegistry = &Registry{}

func init() {
testRegistry.MustRegisterType(NewDefinition(parent{}).Validates(RuleMap{
MustRegisterType(testRegistry, NewDefinition[parent]().Validates(RuleMap{
"Child": {presentRule{}},
"Primitive": {presentRule{}},
"Basic": {presentRule{}},
Expand All @@ -77,10 +77,10 @@ func init() {
"ArrayValidates": {},
"ArrayPtValidates": {},
}))
testRegistry.MustRegisterType(NewDefinition(Child{}).Validates(RuleMap{
MustRegisterType(testRegistry, NewDefinition[Child]().Validates(RuleMap{
"Validates": {presentRule{}},
}))
testRegistry.MustRegisterType(NewDefinition(topLevelValidates{}).ValidatesTopLevel(presentRule{}))
MustRegisterType(testRegistry, NewDefinition[topLevelValidates]().ValidatesTopLevel(presentRule{}))
}

type integrationTestCase struct {
Expand Down
70 changes: 41 additions & 29 deletions pkg/firm/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,68 +5,80 @@ import (
"reflect"
)

type registryValidator interface {
Validator
Rules() []Rule
}

// Registry registers types find the right validator to validate with
type Registry struct {
typeToValidator map[reflect.Type]*ValueValidator
unregisteredTypeReferences map[reflect.Type][]*[]Rule
DefaultValidator Validator
typeToValidator map[reflect.Type]registryValidator // *ValueValidator
unregisteredTypeRefs map[reflect.Type][]*[]Rule
DefaultValidator Validator
}

// MustRegisterType registers the Definition to validate the type, panics if there is an error
func (r *Registry) MustRegisterType(definition *Definition) {
if err := r.RegisterType(definition); err != nil {
func MustRegisterType[T any](r *Registry, definition *Definition[T]) {
if err := RegisterType[T](r, definition); err != nil {
panic(err.Error())
}
}

// RegisterType registers the Definition to validate the type
func (r *Registry) RegisterType(definition *Definition) error {
func RegisterType[T any](r *Registry, definition *Definition[T]) error {
if r.typeToValidator == nil {
r.typeToValidator = map[reflect.Type]*ValueValidator{}
r.unregisteredTypeReferences = map[reflect.Type][]*[]Rule{}
r.typeToValidator = map[reflect.Type]registryValidator{}
r.unregisteredTypeRefs = map[reflect.Type][]*[]Rule{}
}

typ := definition.typ
var zero T
typ := reflect.TypeOf(zero)
if _, exists := r.typeToValidator[typ]; exists {
return fmt.Errorf("RegisterType() with type %v already exists", typ.String())
}

structValidator := mustNewStructValidator(typ, definition.RuleMap())
for fieldName := range structValidator.ruleMap {
field, _ := typ.FieldByName(fieldName)
r.registerRecursionType(field.Type, structValidator.ruleMap[fieldName])
r.typeToValidator[typ] = registeredStructValidator[T](r, definition.TopLevelRules(), definition.RuleMap())
for _, rules := range r.unregisteredTypeRefs[typ] {
*rules = append(*rules, r.typeToValidator[typ])
}
delete(r.unregisteredTypeRefs, typ)
return nil
}

validator := mustNewValueValidator(typ, append(definition.TopLevelRules(), &structValidator)...)
r.typeToValidator[typ] = &validator
func registeredStructValidator[T any](r *Registry, topLevelRules []Rule, ruleMap RuleMap) *ValueValidator[T] {
valueValidatorRules := topLevelRules
if len(ruleMap) > 0 {
structValidator := MustNewStructValidator[T](ruleMap)

for _, rules := range r.unregisteredTypeReferences[typ] {
*rules = append(*rules, r.typeToValidator[typ])
var zero T
typ := indirectType(reflect.TypeOf(zero))
for fieldName := range structValidator.ruleMap {
field, _ := typ.FieldByName(fieldName)
registerRecursionType(r, field.Type, structValidator.ruleMap[fieldName])
}
valueValidatorRules = append(valueValidatorRules, structValidator)
}
delete(r.unregisteredTypeReferences, typ)
return nil
v := MustNewValueValidator[T](valueValidatorRules...)
return &v
}

func (r *Registry) registerRecursionType(typ reflect.Type, rules *[]Rule) {
func registerRecursionType(r *Registry, typ reflect.Type, rules *[]Rule) {
typ = indirectType(typ)

//nolint:exhaustive // just need these cases
switch typ.Kind() {
case reflect.Struct:
validator := r.typeToValidator[typ]
if validator != nil {
*rules = append(*rules, validator.rules...)
if validator == nil {
// when type is registered, appends to the unregisteredTypeRef, similar to inside the else statement
r.unregisteredTypeRefs[typ] = append(r.unregisteredTypeRefs[typ], rules)
} else {
references, exists := r.unregisteredTypeReferences[typ]
if !exists {
references = []*[]Rule{}
}
r.unregisteredTypeReferences[typ] = append(references, rules)
*rules = append(*rules, validator.Rules()...) // add existing type rules
}
case reflect.Slice, reflect.Array:
validator := mustNewSliceValidator(typ)
validator := mustNewValidator(func() (sliceValidator, error) { return newSliceValidator(typ) })
*rules = append(*rules, &validator)
r.registerRecursionType(typ.Elem(), &validator.elementRules)
registerRecursionType(r, typ.Elem(), &validator.elementRules)
}
}

Expand Down
Loading

0 comments on commit c30e906

Please sign in to comment.