package common import ( "encoding/json" "errors" "fmt" "io" "net/http" "strings" "veza-backend-api/internal/response" "veza-backend-api/internal/validators" "github.com/gin-gonic/gin" "github.com/go-playground/validator/v10" ) // MaxJSONBodySize définit la taille maximale du body JSON (10MB par défaut) const MaxJSONBodySize = 10 * 1024 * 1024 // 10MB // BindAndValidateJSON lie et valide les données JSON de la requête de manière robuste // MOD-P1-002: Helper centralisé pour bind + validate + format d'erreur standard // Utilisable par tous les handlers (pas seulement ceux avec CommonHandler) // // Comportement: // - Vérifie la taille du body (max 10MB) // - Parse le JSON avec ShouldBindJSON (Gin) // - Valide avec le validator centralisé (go-playground/validator) // - Retourne false si erreur (erreur déjà envoyée au client avec code 400) // // Usage: // // var req MyRequest // if !common.BindAndValidateJSON(c, &req) { // return // Erreur déjà envoyée au client // } func BindAndValidateJSON(c *gin.Context, obj interface{}) bool { // 1. Vérifier la taille du body if c.Request.ContentLength > MaxJSONBodySize { response.BadRequest(c, fmt.Sprintf("Request body too large: maximum size is %d bytes", MaxJSONBodySize)) return false } // 2. Limiter la lecture du body pour éviter les attaques par body trop gros c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, MaxJSONBodySize) // 3. Parser le JSON avec ShouldBindJSON if err := c.ShouldBindJSON(obj); err != nil { handleBindingError(c, err) return false } // 4. Valider avec le validator centralisé (go-playground/validator) validator := validators.NewValidator() validationErrors := validator.Validate(obj) if len(validationErrors) > 0 { // Convertir en format standardisé details := make(map[string]string) for _, ve := range validationErrors { details[ve.Field] = ve.Message } response.ValidationError(c, "Validation failed", details) return false } return true } // BindAndValidate binds the request body to the given object and validates it. // DEPRECATED: Utiliser BindAndValidateJSON à la place pour une validation complète // If binding or validation fails, it sends a standardized error response and returns false. // Returns true if successful. func BindAndValidate(c *gin.Context, obj interface{}) bool { return BindAndValidateJSON(c, obj) } // handleBindingError gère les erreurs de binding JSON // MOD-P1-002: Gestion d'erreurs améliorée pour JSON malformé func handleBindingError(c *gin.Context, err error) { var jsonSyntaxError *json.SyntaxError var jsonUnmarshalTypeError *json.UnmarshalTypeError var maxBytesError *http.MaxBytesError var ve validator.ValidationErrors switch { case errors.As(err, &maxBytesError): // Body trop gros response.BadRequest(c, fmt.Sprintf("Request body too large: maximum size is %d bytes", MaxJSONBodySize)) case errors.As(err, &jsonSyntaxError): // JSON syntaxiquement invalide response.BadRequest(c, fmt.Sprintf("Invalid JSON syntax at offset %d: %s", jsonSyntaxError.Offset, jsonSyntaxError.Error())) case errors.As(err, &jsonUnmarshalTypeError): // Type incorrect pour un champ response.BadRequest(c, fmt.Sprintf("Invalid type for field '%s': expected %s", jsonUnmarshalTypeError.Field, jsonUnmarshalTypeError.Type.String())) case errors.Is(err, io.EOF): // Body vide response.BadRequest(c, "Request body is empty or invalid JSON") case errors.Is(err, io.ErrUnexpectedEOF): // JSON incomplet response.BadRequest(c, "Incomplete or malformed JSON") case errors.As(err, &ve): // Erreurs de validation Gin (binding tags) details := make(map[string]string) for _, fe := range ve { details[fe.Field()] = msgForTag(fe) } response.ValidationError(c, "Validation failed", details) default: // Erreur générique errStr := err.Error() if strings.Contains(errStr, "unknown field") { response.BadRequest(c, "Unknown fields in JSON payload") } else { response.BadRequest(c, "Invalid request body format") } } } func msgForTag(fe validator.FieldError) string { switch fe.Tag() { case "required": return "This field is required" case "email": return "Invalid email format" case "min": return fmt.Sprintf("Must be at least %s characters long", fe.Param()) case "max": return fmt.Sprintf("Must be at most %s characters long", fe.Param()) case "uuid": return "Invalid UUID format" default: return fmt.Sprintf("Failed validation on tag: %s", fe.Tag()) } }