Skip to content

Commit

Permalink
feature: typed arguments
Browse files Browse the repository at this point in the history
  • Loading branch information
strider2038 committed Jun 21, 2022
1 parent 2d1ab08 commit e725b31
Show file tree
Hide file tree
Showing 3 changed files with 27 additions and 21 deletions.
17 changes: 17 additions & 0 deletions arguments.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,23 @@ func NewArgument(validate ValidateOnScopeFunc) ValidatorArgument {
return ValidatorArgument{validate: validate}
}

// NewTypedArgument creates a generic validation argument that can help implement the validation
// argument for client-side types.
func NewTypedArgument[T any](v T, constraints ...Constraint[T]) ValidatorArgument {
return NewArgument(func(scope Scope) (*ViolationList, error) {
violations := NewViolationList()

for _, constraint := range constraints {
err := violations.AppendFromError(constraint.Validate(v, scope))
if err != nil {
return nil, err
}
}

return violations, nil
})
}

// ValidatorArgument is common implementation of Argument that is used to run validation
// process on given argument.
type ValidatorArgument struct {
Expand Down
5 changes: 5 additions & 0 deletions contraint.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ type Numeric interface {
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
}

// Constraint is a generic interface for client-side typed constraints.
type Constraint[T any] interface {
Validate(v T, scope Scope) error
}

// NilConstraint is used for a special cases to check a value for nil.
type NilConstraint interface {
ValidateNil(isNil bool, scope Scope) error
Expand Down
26 changes: 5 additions & 21 deletions example_custom_argument_constraint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,34 +28,18 @@ func (repository *BrandRepository) FindByName(ctx context.Context, name string)
return found, nil
}

// You can declare you own constraint interface to create custom constraints.
type BrandConstraint interface {
ValidateBrand(brand *Brand, scope validation.Scope) error
}

// To create your own functional argument for validation simply create a function with
// a typed value and use the validation.NewArgument constructor.
func ValidBrand(brand *Brand, constraints ...BrandConstraint) validation.ValidatorArgument {
return validation.NewArgument(func(scope validation.Scope) (*validation.ViolationList, error) {
violations := validation.NewViolationList()

for i := range constraints {
err := violations.AppendFromError(constraints[i].ValidateBrand(brand, scope))
if err != nil {
return nil, err
}
}

return violations, nil
})
// a typed value and use the validation.NewTypedArgument constructor.
func ValidBrand(brand *Brand, constraints ...validation.Constraint[*Brand]) validation.ValidatorArgument {
return validation.NewTypedArgument[*Brand](brand, constraints...)
}

// UniqueBrandConstraint implements BrandConstraint.
type UniqueBrandConstraint struct {
brands *BrandRepository
}

func (c *UniqueBrandConstraint) ValidateBrand(brand *Brand, scope validation.Scope) error {
func (c *UniqueBrandConstraint) Validate(brand *Brand, scope validation.Scope) error {
// usually, you should ignore empty values
// to check for an empty value you should use it.NotBlankConstraint
if brand == nil {
Expand All @@ -81,7 +65,7 @@ func (c *UniqueBrandConstraint) ValidateBrand(brand *Brand, scope validation.Sco
Create()
}

func ExampleNewArgument_customArgumentConstraintValidator() {
func ExampleNewTypedArgument_customArgumentConstraintValidator() {
repository := &BrandRepository{brands: []Brand{{"Apple"}, {"Orange"}}}
isEntityUnique := &UniqueBrandConstraint{brands: repository}

Expand Down

0 comments on commit e725b31

Please sign in to comment.