Skip to content

Commit

Permalink
fixed error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
aymaneallaoui committed Sep 13, 2024
1 parent 60c6a93 commit 8ae0fd9
Show file tree
Hide file tree
Showing 7 changed files with 194 additions and 71 deletions.
74 changes: 61 additions & 13 deletions examples/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,66 @@ import (

func main() {

// Define a string schema with specific messages
stringSchema := validators.String().Min(3).Max(5).Required().
WithMessage("minLength", "This string is too short!").
WithMessage("maxLength", "This string is too long!")
WithMessage("minLength", "The string is too short! Minimum length is 3.").
WithMessage("maxLength", "The string is too long! Maximum length is 5.").
WithMessage("required", "A string value is required.")

err := stringSchema.Validate("arch")
// Test string validation
err := stringSchema.Validate("ab")
if err != nil {
fmt.Println("Validation failed:", err.(*zod.ValidationError).Error())
fmt.Println("String validation failed:", err.(*zod.ValidationError).Error())
fmt.Println("String validation failed (JSON):", err.(*zod.ValidationError).ErrorJSON())
} else {
fmt.Println("Validation succeeded")
fmt.Println("String validation succeeded")
}

// Define a number schema with specific messages
numberSchema := validators.Number().Min(18).Max(65).Required().
WithMessage("min", "Age must be at least 18.").
WithMessage("max", "Age must be at most 65.").
WithMessage("required", "An age value is required.")

// Test number validation
err = numberSchema.Validate(17)
if err != nil {
fmt.Println("Number validation failed:", err.(*zod.ValidationError).Error())
fmt.Println("Number validation failed (JSON):", err.(*zod.ValidationError).ErrorJSON())
} else {
fmt.Println("Number validation succeeded")
}

// Define a boolean schema with specific messages
boolSchema := validators.Bool().Required().
WithMessage("required", "A boolean value is required.")

// Test boolean validation
err = boolSchema.Validate(false)
if err != nil {
fmt.Println("Boolean validation failed:", err.(*zod.ValidationError).Error())
fmt.Println("Boolean validation failed (JSON):", err.(*zod.ValidationError).ErrorJSON())
} else {
fmt.Println("Boolean validation succeeded")
}

// Define an array schema with specific messages
arraySchema := validators.Array(validators.String().Min(3).Max(5)).
Min(1).Max(3).Required().
WithMessage("min", "The array must have at least 1 element.").
WithMessage("max", "The array must have no more than 3 elements.").
WithMessage("required", "An array is required.")

// Test array validation
err = arraySchema.Validate([]interface{}{"short", "this is too long", "valid"})
if err != nil {
fmt.Println("Array validation failed:", err.(*zod.ValidationError).Error())
fmt.Println("Array validation failed (JSON):", err.(*zod.ValidationError).ErrorJSON())
} else {
fmt.Println("Array validation succeeded")
}

// Define a complex object schema with nested fields and validation
userSchema := validators.Object(map[string]zod.Schema{
"name": validators.String().Min(3).Required().
WithMessage("required", "Name is a required field!").
Expand All @@ -33,21 +82,20 @@ func main() {
WithMessage("minLength", "Street must be at least 5 characters."),
"city": validators.String().Min(3).Max(30).Required().
WithMessage("required", "City is required."),
}).Required(),
}).Required().WithMessage("required", "Address is required."),
})

// Test object schema validation with nested fields
userData := map[string]interface{}{
"name": "aymane",
"age": 21,
"address": map[string]interface{}{
"street": "lol idk test ",
"city": "",
},
"name": "ay",
"age": 17,
"address": map[string]interface{}{"street": "", "city": ""},
}

err = userSchema.Validate(userData)
if err != nil {
fmt.Println("User validation failed:", err.(*zod.ValidationError).ErrorJSON())
fmt.Println("User validation failed:", err.(*zod.ValidationError).Error())
fmt.Println("User validation failed (JSON):", err.(*zod.ValidationError).ErrorJSON())
} else {
fmt.Println("User validation succeeded")
}
Expand Down
95 changes: 75 additions & 20 deletions zod/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,34 +6,89 @@ import (
)

type ValidationError struct {
Field string `json:"field"`
Value interface{} `json:"value"`
Message string `json:"message"`
Details []ValidationError `json:"details,omitempty"`
}

func (e *ValidationError) Error() string {
return fmt.Sprintf("Validation error on field '%s': %s (Value: %v)", e.Field, e.Message, e.Value)
}

func (e *ValidationError) ErrorJSON() string {
jsonData, _ := json.MarshalIndent(e, "", " ")
return string(jsonData)
ErrorType string `json:"errorType"`
Message string `json:"message"`
Field string `json:"field"`
Value interface{} `json:"value"`
Details []ValidationError `json:"details,omitempty"`
}

// NewValidationError creates a new simple validation error
func NewValidationError(field string, value interface{}, message string) *ValidationError {
return &ValidationError{
Field: field,
Value: value,
Message: message,
ErrorType: "Validation Error",
Message: message, // Directly store the relevant message without extra formatting
Field: field,
Value: value,
}
}

// NewNestedValidationError creates a new nested validation error
func NewNestedValidationError(field string, value interface{}, message string, details []ValidationError) *ValidationError {
return &ValidationError{
Field: field,
Value: value,
Message: message,
Details: details,
ErrorType: "Nested Validation Error",
Message: message,
Field: field,
Value: value,
Details: details,
}
}

// Error returns a human-readable string for the top-level error and its nested details
func (v *ValidationError) Error() string {
if len(v.Details) > 0 {
return v.formatNestedError()
}
return fmt.Sprintf("Field: %s, Error: %s", v.Field, v.Message)
}

// formatNestedError recursively formats the nested validation errors
func (v *ValidationError) formatNestedError() string {
var result string
for _, detail := range v.Details {
result += fmt.Sprintf("Field: %s, Error: %s\n", detail.Field, detail.Message)
}
return result
}

// ErrorJSON returns the JSON representation of the error with only the message
func (v *ValidationError) ErrorJSON() string {
// If there are nested details, we need to flatten them
if len(v.Details) > 0 {
return v.flattenNestedErrors()
}

// Return simple error if there are no nested details
errJSON, _ := json.Marshal(map[string]string{
"field": v.Field,
"message": v.Message, // Use only the message itself, no "Field: string, Error: ..." formatting
})
return string(errJSON)
}

// flattenNestedErrors returns a simplified JSON with just the most relevant error messages
func (v *ValidationError) flattenNestedErrors() string {
var flatErrors []map[string]string

// Collect errors from both top-level and nested errors
v.collectErrors(&flatErrors)

flatErrorJSON, _ := json.Marshal(flatErrors)
return string(flatErrorJSON)
}

// collectErrors recursively collects both top-level and nested validation errors
func (v *ValidationError) collectErrors(flatErrors *[]map[string]string) {
// Add the current error to the list
if v.Field != "" && v.Message != "" {
*flatErrors = append(*flatErrors, map[string]string{
"field": v.Field,
"message": v.Message, // Only use the clean message without extra formatting
})
}

// Recursively collect any nested errors
for _, detail := range v.Details {
detail.collectErrors(flatErrors)
}
}
24 changes: 20 additions & 4 deletions zod/validators/bool.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,42 @@ package validators
import "github.com/aymaneallaoui/zod-Go/zod"

type BoolSchema struct {
required bool
required bool
customError map[string]string
}

func Bool() *BoolSchema {
return &BoolSchema{}
return &BoolSchema{
customError: make(map[string]string),
}
}

func (b *BoolSchema) Required() *BoolSchema {
b.required = true
return b
}

// Add WithMessage method for BoolSchema
func (b *BoolSchema) WithMessage(validationType, message string) *BoolSchema {
b.customError[validationType] = message
return b
}

func (b *BoolSchema) getErrorMessage(validationType, defaultMessage string) string {
if msg, exists := b.customError[validationType]; exists {
return msg
}
return defaultMessage
}

func (b *BoolSchema) Validate(data interface{}) error {
val, ok := data.(bool)
if !ok {
return zod.NewValidationError("bool", data, "invalid type, expected boolean")
return zod.NewValidationError("bool", data, b.getErrorMessage("type", "invalid type, expected boolean"))
}

if b.required && !val {
return zod.NewValidationError("bool", data, "boolean is required")
return zod.NewValidationError("bool", data, b.getErrorMessage("required", "boolean is required"))
}

return nil
Expand Down
10 changes: 1 addition & 9 deletions zod/validators/map.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,7 @@ func (m *MapSchema) Required() *MapSchema {
func (m *MapSchema) Validate(data interface{}) error {
mapData, ok := data.(map[interface{}]interface{})
if !ok {
switch v := data.(type) {
case map[string]interface{}:
mapData = make(map[interface{}]interface{}, len(v))
for k, val := range v {
mapData[k] = val
}
default:
return zod.NewValidationError("map", data, "invalid type, expected map")
}
return zod.NewValidationError("map", data, "invalid type, expected map")
}

for key, value := range mapData {
Expand Down
42 changes: 22 additions & 20 deletions zod/validators/slice.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@ type ArraySchema struct {
minLength int
maxLength int
required bool
customError map[string]string
}

func Array(elementSchema zod.Schema) *ArraySchema {
return &ArraySchema{elementSchema: elementSchema}
return &ArraySchema{
elementSchema: elementSchema,
customError: make(map[string]string),
}
}

func (a *ArraySchema) Min(length int) *ArraySchema {
Expand All @@ -32,37 +36,35 @@ func (a *ArraySchema) Required() *ArraySchema {
return a
}

func (a *ArraySchema) Validate(data interface{}) error {
// Add WithMessage method for ArraySchema
func (a *ArraySchema) WithMessage(validationType, message string) *ArraySchema {
a.customError[validationType] = message
return a
}

func (a *ArraySchema) getErrorMessage(validationType, defaultMessage string) string {
if msg, exists := a.customError[validationType]; exists {
return msg
}
return defaultMessage
}

func (a *ArraySchema) Validate(data interface{}) error {
array, ok := data.([]interface{})
if !ok {

switch v := data.(type) {
case []string:
array = make([]interface{}, len(v))
for i := range v {
array[i] = v[i]
}
case []int:
array = make([]interface{}, len(v))
for i := range v {
array[i] = v[i]
}
default:
return zod.NewValidationError("array", data, "invalid type, expected array")
}
return zod.NewValidationError("array", data, a.getErrorMessage("type", "invalid type, expected array"))
}

if a.required && len(array) == 0 {
return zod.NewValidationError("array", data, "array is required")
return zod.NewValidationError("array", data, a.getErrorMessage("required", "array is required"))
}

if len(array) < a.minLength {
return zod.NewValidationError("array", data, "array is too short")
return zod.NewValidationError("array", data, a.getErrorMessage("min", "array is too short"))
}

if len(array) > a.maxLength {
return zod.NewValidationError("array", data, "array is too long")
return zod.NewValidationError("array", data, a.getErrorMessage("max", "array is too long"))
}

var wg sync.WaitGroup
Expand Down
4 changes: 4 additions & 0 deletions zod/validators/string.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,18 +49,22 @@ func (s *StringSchema) getErrorMessage(validationType, defaultMessage string) st
func (s *StringSchema) Validate(data interface{}) error {
str, ok := data.(string)
if !ok {
// Pass a clean message
return zod.NewValidationError("string", data, s.getErrorMessage("type", "invalid type, expected string"))
}

if s.required && str == "" {
// Pass only the clean message
return zod.NewValidationError("string", data, s.getErrorMessage("required", "string is required"))
}

if len(str) < s.minLength {
// Pass only the clean message
return zod.NewValidationError("string", str, s.getErrorMessage("minLength", fmt.Sprintf("string is too short, minimum length is %d", s.minLength)))
}

if s.maxLength > 0 && len(str) > s.maxLength {
// Pass only the clean message
return zod.NewValidationError("string", str, s.getErrorMessage("maxLength", fmt.Sprintf("string is too long, maximum length is %d", s.maxLength)))
}

Expand Down
Loading

0 comments on commit 8ae0fd9

Please sign in to comment.