Files
chess/internal/http/validator.go

103 lines
3.0 KiB
Go

// FILE: internal/http/handler.go
package http
import (
"fmt"
"reflect"
"strings"
"chess/internal/core"
"github.com/go-playground/validator/v10"
"github.com/gofiber/fiber/v2"
"github.com/google/uuid"
)
// Add validator instance near top of file
var validate = validator.New()
// Add custom validation middleware function
func validationMiddleware(c *fiber.Ctx) error {
// Skip validation for GET, DELETE, OPTIONS
method := c.Method()
if method == fiber.MethodGet || method == fiber.MethodDelete || method == fiber.MethodOptions {
return c.Next()
}
// Determine request type based on path
path := c.Path()
var requestType interface{}
switch {
case strings.HasSuffix(path, "/games") && method == fiber.MethodPost:
requestType = &core.CreateGameRequest{}
case strings.HasSuffix(path, "/players") && method == fiber.MethodPut:
requestType = &core.ConfigurePlayersRequest{}
case strings.HasSuffix(path, "/moves") && method == fiber.MethodPost:
requestType = &core.MoveRequest{}
case strings.HasSuffix(path, "/undo") && method == fiber.MethodPost:
requestType = &core.UndoRequest{}
default:
return c.Next() // No validation for unknown endpoints
}
// Parse body
if err := c.BodyParser(requestType); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(core.ErrorResponse{
Error: "invalid request body",
Code: core.ErrInvalidRequest,
Details: err.Error(),
})
}
// Validate
if errs := validate.Struct(requestType); errs != nil {
var details strings.Builder
for _, err := range errs.(validator.ValidationErrors) {
if details.Len() > 0 {
details.WriteString("; ")
}
switch err.Tag() {
case "required":
details.WriteString(fmt.Sprintf("%s is required", err.Field()))
case "oneof":
details.WriteString(fmt.Sprintf("%s must be one of [%s]", err.Field(), err.Param()))
case "min":
if err.Type().Kind() == reflect.String {
details.WriteString(fmt.Sprintf("%s must be at least %s characters", err.Field(), err.Param()))
} else {
details.WriteString(fmt.Sprintf("%s must be at least %s", err.Field(), err.Param()))
}
case "max":
if err.Type().Kind() == reflect.String {
details.WriteString(fmt.Sprintf("%s must be at most %s characters", err.Field(), err.Param()))
} else {
details.WriteString(fmt.Sprintf("%s must be at most %s", err.Field(), err.Param()))
}
case "omitempty": // Skip, a control tag that doesn't error
continue
case "dive": // Skip, panics on wrong type, no error handling since current code does not call validator on slice or map
continue
default:
details.WriteString(fmt.Sprintf("%s failed %s validation", err.Field(), err.Tag()))
}
}
return c.Status(fiber.StatusBadRequest).JSON(core.ErrorResponse{
Error: "validation failed",
Code: core.ErrInvalidRequest,
Details: details.String(),
})
}
// Store validated body for handler use
c.Locals("validatedBody", requestType)
c.Locals("validated", true)
return c.Next()
}
func isValidUUID(s string) bool {
_, err := uuid.Parse(s)
return err == nil
}