Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 33 additions & 13 deletions validator/range.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,49 @@ import (
"reflect"
)

// MinLength checks if a string meets a minimum length requirement.
// MinLength checks if a string, slice, or array meets a minimum length requirement.
func MinLength(min int) ValidatorFunc {
return func(value interface{}) error {
str, ok := value.(string)
if !ok {
return errors.New("value is not a string")
if value == nil {
return errors.New("value is nil")
}
if len(str) < min {
return fmt.Errorf("value must be at least %d characters long", min)

v := reflect.ValueOf(value)
switch v.Kind() {
case reflect.String:
if v.Len() < min {
return fmt.Errorf("value must be at least %d characters long", min)
}
case reflect.Slice, reflect.Array:
if v.Len() < min {
return fmt.Errorf("value must have at least %d elements", min)
}
default:
return fmt.Errorf("value must be a string, slice, or array, got %T", value)
}
return nil
}
}

// MaxLength checks if a string meets a maximum length requirement.
// MaxLength checks if a string, slice, or array meets a maximum length requirement.
func MaxLength(max int) ValidatorFunc {
return func(value interface{}) error {
str, ok := value.(string)
if !ok {
return errors.New("value is not a string")
if value == nil {
return errors.New("value is nil")
}
if len(str) > max {
return fmt.Errorf("value must be at most %d characters long", max)

v := reflect.ValueOf(value)
switch v.Kind() {
case reflect.String:
if v.Len() > max {
return fmt.Errorf("value must be at most %d characters long", max)
}
case reflect.Slice, reflect.Array:
if v.Len() > max {
return fmt.Errorf("value must have at most %d elements", max)
}
default:
return fmt.Errorf("value must be a string, slice, or array, got %T", value)
}
return nil
}
Expand Down Expand Up @@ -134,7 +154,7 @@ func EachWithOptions(options []ValidationOption) ValidatorFunc {
}
}
if err := Validate(nestedBody, options); err != nil {
return fmt.Errorf("element at index %d: %v", i, err)
return err
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

This change breaks existing tests.

Removing the element index from the error message breaks the tests in TestEachWithOptions that expect errors to include the index (see pipeline failures).

-				return err
+				return fmt.Errorf("element at index %d: %v", i, err)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return err
return fmt.Errorf("element at index %d: %v", i, err)

}
}
return nil
Expand Down
221 changes: 219 additions & 2 deletions validator/range_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,15 +183,15 @@ func TestEachWithOptions(t *testing.T) {
map[string]interface{}{"name": "color", "value": "red"},
map[string]interface{}{"value": "M"},
},
expected: errors.New("element at index 1: name is required"),
expected: errors.New("name is required"),
},
{
name: "invalid array - wrong type",
input: []interface{}{
map[string]interface{}{"name": "color", "value": "red"},
map[string]interface{}{"name": 123, "value": "M"},
},
expected: errors.New("element at index 1: value must be a string"),
expected: errors.New("value must be a string"),
},
{
name: "empty array",
Expand Down Expand Up @@ -255,3 +255,220 @@ func TestEachWithOptions(t *testing.T) {
})
}
}

var ProductVariantsValidationOptions = []ValidationOption{
{
Key: "options",
IsOptional: true,
Validators: []Validator{
CreateValidator(EachWithOptions([]ValidationOption{
{
Key: "name",
IsOptional: false,
Transformers: []Transformer{
ToLower,
},
Validators: []Validator{
CreateValidator(IsNotEmpty, "Variant option name is required"),
CreateValidator(MaxLength(150), "Variant option name must be less than 150 characters"),
},
},
{
Key: "values",
IsOptional: false,
Validators: []Validator{
CreateValidator(MinLength(1), "At least one option value is required"),
CreateValidator(Each(MaxLength(150)), "Each option value must be less than 150 characters"),
},
},
}), ""),
},
},
{
Key: "values",
IsOptional: true,
Validators: []Validator{
CreateValidator(MinLength(1), "At least one variant value is required"),
CreateValidator(EachWithOptions([]ValidationOption{
{
Key: "options",
IsOptional: false,
Validators: []Validator{
CreateValidator(MinLength(1), "At least one variant option is required"),
CreateValidator(EachWithOptions([]ValidationOption{
{
Key: "name",
IsOptional: false,
Validators: []Validator{
CreateValidator(IsNotEmpty, "Variant option name is required"),
CreateValidator(MaxLength(150), "Variant option name must be less than 150 characters"),
},
},
{
Key: "value",
IsOptional: false,
Validators: []Validator{
CreateValidator(IsNotEmpty, "Variant option value is required"),
CreateValidator(MaxLength(150), "Variant option value must be less than 150 characters"),
},
},
}), ""),
},
},
{
Key: "sku",
IsOptional: false,
Validators: []Validator{
CreateValidator(IsNotEmpty, "SKU is required"),
CreateValidator(MinLength(1), "SKU must be at least 1 character"),
CreateValidator(MaxLength(50), "SKU must be less than 50 characters"),
},
},
{
Key: "reference",
IsOptional: true,
Validators: []Validator{
CreateValidator(MaxLength(150), "Reference must be less than 150 characters"),
},
},
{
Key: "unitPrice",
IsOptional: false,
Validators: []Validator{
CreateValidator(IsFloat, "Unit price is required"),
CreateValidator(Min(0), "Unit price must be at least 0"),
},
},
{
Key: "costPrice",
IsOptional: false,
Validators: []Validator{
CreateValidator(IsFloat, "Cost price is required"),
CreateValidator(Min(0), "Cost price must be at least 0"),
},
},
{
Key: "image",
IsOptional: true,
Validators: []Validator{
CreateValidator(IsURL, "Image must be a valid URL"),
},
},
{
Key: "quantityPricing",
IsOptional: true,
Validators: []Validator{
CreateValidator(EachWithOptions([]ValidationOption{
{
Key: "minQuantity",
IsOptional: false,
Validators: []Validator{
CreateValidator(IsInt, "Min quantity must be an integer"),
CreateValidator(Min(1), "Min quantity must be at least 1"),
},
},
{
Key: "price",
IsOptional: false,
Validators: []Validator{
CreateValidator(IsFloat, "Price is required"),
CreateValidator(Min(0), "Price must be at least 0"),
},
},
}), "Invalid quantity pricing"),
},
},
}), ""),
},
},
}

func TestProductVariantsValidationOptions(t *testing.T) {
// Define test cases
tests := []struct {
description string
input map[string]interface{}
expectedErr string
}{
{
description: "should fail when name is empty",
input: map[string]interface{}{
"options": []map[string]interface{}{
{
"name": "",
"values": []string{"small", "medium"},
},
},
},
expectedErr: "Variant option name is required",
},
{
description: "should fail when name exceeds 150 characters",
input: map[string]interface{}{
"options": []map[string]interface{}{
{
"name": string(make([]byte, 151)), // 151 chars
"values": []string{"small", "medium"},
},
},
},
expectedErr: "Variant option name must be less than 150 characters",
},
{
description: "should fail when values is empty",
input: map[string]interface{}{
"options": []map[string]interface{}{
{
"name": "size",
"values": []string{},
},
},
},
expectedErr: "At least one option value is required",
},
{
description: "should fail when a value exceeds 150 characters",
input: map[string]interface{}{
"options": []map[string]interface{}{
{
"name": "size",
"values": []string{"small", string(make([]byte, 151))},
},
},
},
expectedErr: "Each option value must be less than 150 characters",
},
{
description: "should pass with valid input",
input: map[string]interface{}{
"options": []map[string]interface{}{
{
"name": "size",
"values": []string{"small", "medium"},
},
},
},
expectedErr: "",
},
}

// Run tests
for _, test := range tests {
t.Run(test.description, func(t *testing.T) {
err := Validate(test.input, ProductVariantsValidationOptions)
if test.expectedErr == "" {
// Expect no error
if err != nil {
t.Errorf("expected no error, got: %v", err)
}
} else {
// Expect an error
if err == nil {
t.Errorf("expected error %q, got nil", test.expectedErr)
} else if err.Error() != test.expectedErr {
t.Errorf("expected error %q, got %q", test.expectedErr, err.Error())
}
}
})
}
}
3 changes: 3 additions & 0 deletions validator/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ func Validate(body map[string]interface{}, options []ValidationOption) error {
// Run all validators for the field (if it exists)
for _, validator := range option.Validators {
if err := validator.Func(value); err != nil {
if validator.Message == "" {
return err
}
return fmt.Errorf("%s", validator.Message)
}
}
Expand Down
Loading