102 lines
3.0 KiB
Go
102 lines
3.0 KiB
Go
package http
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
|
|
"chess/internal/server/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 any
|
|
|
|
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
|
|
} |