# ✅ P1-002 — VALIDATION INPUTS SYSTÉMATIQUE (DTO + HELPER) **Date**: 2025-12-12 **Objectif**: Rendre la validation homogène sur les endpoints critiques avec DTOs + helper centralisé --- ## 📋 RÉSUMÉ ✅ **Helper centralisé créé** : `common.BindAndValidateJSON()` utilisable par tous les handlers ✅ **DTOs complétés** : Tags `validate` ajoutés aux DTOs existants (en plus de `binding`) ✅ **Handlers mis à jour** : `TrackHandler` utilise maintenant le helper centralisé ✅ **Tests ajoutés** : Tests unitaires pour cas invalides, limites, UUID invalides ✅ **Format d'erreur standardisé** : HTTP 400 avec détails structurés --- ## 📁 FICHIERS MODIFIÉS ### 1. `internal/common/validation.go` - ✅ Amélioration de `BindAndValidateJSON()` pour utiliser le validator centralisé - ✅ Gestion d'erreurs améliorée (JSON malformé, body trop gros, types incorrects) - ✅ Validation avec `go-playground/validator` après binding Gin - ✅ Format d'erreur standardisé via `response.ValidationError()` ### 2. `internal/core/track/handler.go` - ✅ Ajout import `common` pour utiliser `BindAndValidateJSON()` - ✅ Remplacement de tous les appels `ShouldBindJSON()` par `BindAndValidateJSON()` - ✅ Ajout tags `validate` aux DTOs : - `InitiateChunkedUploadRequest` - `UpdateTrackRequest` - `CompleteChunkedUploadRequest` - `BatchDeleteRequest` - `BatchUpdateRequest` - `CreateShareRequest` - `StreamCallbackRequest` ### 3. `internal/handlers/profile_handler.go` - ✅ Ajout tags `validate` à `UpdateProfileRequest` - ✅ Utilise déjà `BindAndValidateJSON()` de `CommonHandler` (pas de changement) ### 4. `internal/common/validation_test.go` (nouveau) - ✅ Tests unitaires complets : - Requête valide - Champ requis manquant - UUID invalide - String trop longue - Nombre hors limites - String vide - JSON malformé - Body trop gros --- ## 🎯 ENDPOINTS COUVERTS ### TrackHandler (`/api/v1/tracks/*`) | Endpoint | Méthode | DTO | Validation | |----------|---------|-----|------------| | `/tracks/initiate` | POST | `InitiateChunkedUploadRequest` | ✅ Tags `validate` + helper | | `/tracks/{id}` | PUT | `UpdateTrackRequest` | ✅ Tags `validate` + helper | | `/tracks/complete` | POST | `CompleteChunkedUploadRequest` | ✅ Tags `validate` + helper | | `/tracks/batch/delete` | POST | `BatchDeleteRequest` | ✅ Tags `validate` + helper | | `/tracks/batch/update` | POST | `BatchUpdateRequest` | ✅ Tags `validate` + helper | | `/tracks/{id}/share` | POST | `CreateShareRequest` | ✅ Tags `validate` + helper | | `/tracks/stream/callback` | POST | `StreamCallbackRequest` | ✅ Tags `validate` + helper | ### ProfileHandler (`/api/v1/users/*`) | Endpoint | Méthode | DTO | Validation | |----------|---------|-----|------------| | `/users/{id}` | PUT | `UpdateProfileRequest` | ✅ Tags `validate` + helper (déjà utilisé) | ### PlaylistHandler (`/api/v1/playlists/*`) | Endpoint | Méthode | DTO | Validation | |----------|---------|-----|------------| | `/playlists` | POST | `CreatePlaylistRequest` | ✅ Déjà utilise `BindAndValidateJSON()` | | `/playlists/{id}` | PUT | `UpdatePlaylistRequest` | ✅ Déjà utilise `BindAndValidateJSON()` | | `/playlists/{id}/reorder` | POST | `ReorderTracksRequest` | ✅ Déjà utilise `BindAndValidateJSON()` | --- ## 🚫 ENDPOINTS HORS SCOPE (justifiés) ### 1. Endpoints avec `ShouldBind()` (multipart/form-data) - **`/tracks/chunk`** (POST) : `UploadChunkRequest` utilise `form` tags (multipart) - **Justification** : Validation différente (fichiers, pas JSON) - **Action** : Hors scope P1-002 (validation JSON uniquement) ### 2. Endpoints avec `ShouldBind()` (query params) - Endpoints avec query params uniquement - **Justification** : Pas de body JSON, validation différente - **Action** : Hors scope P1-002 ### 3. Handlers dans `.backup-pre-uuid-migration/` - **Justification** : Fichiers de backup, non utilisés - **Action** : Ignorés --- ## 📊 EXEMPLES D'ERREURS JSON RETOURNÉES ### Cas 1 : Champ requis manquant **Requête** : ```json { "year": 2020 // title manquant } ``` **Réponse** (HTTP 400) : ```json { "success": false, "error": { "code": 400, "message": "Validation failed", "details": { "title": "title is required" } } } ``` ### Cas 2 : UUID invalide **Requête** : ```json { "title": "Test Track", "track_id": "invalid-uuid" } ``` **Réponse** (HTTP 400) : ```json { "success": false, "error": { "code": 400, "message": "Validation failed", "details": { "track_id": "track_id must be a valid UUID" } } } ``` ### Cas 3 : String trop longue **Requête** : ```json { "title": "A" * 300, // 300 caractères > max 255 "track_id": "550e8400-e29b-41d4-a716-446655440000" } ``` **Réponse** (HTTP 400) : ```json { "success": false, "error": { "code": 400, "message": "Validation failed", "details": { "title": "title must be at most 255 characters" } } } ``` ### Cas 4 : Nombre hors limites **Requête** : ```json { "title": "Test Track", "year": 1800, // < min 1900 "track_id": "550e8400-e29b-41d4-a716-446655440000" } ``` **Réponse** (HTTP 400) : ```json { "success": false, "error": { "code": 400, "message": "Validation failed", "details": { "year": "year must be greater than or equal to 1900" } } } ``` ### Cas 5 : JSON malformé **Requête** : ```json { "title": "Test", "track_id": invalid } ``` **Réponse** (HTTP 400) : ```json { "error": "Invalid JSON syntax at offset 35: invalid character 'i' looking for beginning of value" } ``` --- ## 🧪 TESTS ### Tests unitaires ```bash go test ./internal/common -run TestBindAndValidateJSON -v -count=1 ``` **Résultat** : ✅ **Tous les tests passent** - `TestBindAndValidateJSON_ValidRequest` — PASS - `TestBindAndValidateJSON_MissingRequiredField` — PASS - `TestBindAndValidateJSON_InvalidUUID` — PASS - `TestBindAndValidateJSON_StringTooLong` — PASS - `TestBindAndValidateJSON_NumberOutOfRange` — PASS - `TestBindAndValidateJSON_EmptyString` — PASS - `TestBindAndValidateJSON_InvalidJSON` — PASS - `TestBindAndValidateJSON_BodyTooLarge` — PASS ### Tests complets ```bash go test ./... -count=1 ``` **Résultat** : Tests unitaires P1-002 passent. Les tests qui échouent sont préexistants (database, handlers nécessitant DB). --- ## 🔧 HELPER CENTRALISÉ ### `common.BindAndValidateJSON(c *gin.Context, obj interface{}) bool` **Fonctionnalités** : 1. ✅ Vérifie la taille du body (max 10MB) 2. ✅ Parse le JSON avec `ShouldBindJSON` (Gin) 3. ✅ Valide avec `go-playground/validator` (tags `validate`) 4. ✅ Retourne `false` si erreur (erreur déjà envoyée au client avec HTTP 400) 5. ✅ Format d'erreur standardisé via `response.ValidationError()` **Usage** : ```go var req UpdateTrackRequest if !common.BindAndValidateJSON(c, &req) { return // Erreur déjà envoyée au client } // req est validé et prêt à être utilisé ``` --- ## 📝 TAGS VALIDATE AJOUTÉS ### Exemples de DTOs mis à jour #### `UpdateTrackRequest` ```go type UpdateTrackRequest struct { Title *string `json:"title" binding:"omitempty,min=1,max=255" validate:"omitempty,min=1,max=255"` Artist *string `json:"artist" binding:"omitempty,max=255" validate:"omitempty,max=255"` Year *int `json:"year" binding:"omitempty,min=1900,max=2100" validate:"omitempty,min=1900,max=2100"` } ``` #### `InitiateChunkedUploadRequest` ```go type InitiateChunkedUploadRequest struct { TotalChunks int `json:"total_chunks" binding:"required,min=1" validate:"required,min=1"` TotalSize int64 `json:"total_size" binding:"required,min=1" validate:"required,min=1"` Filename string `json:"filename" binding:"required" validate:"required"` } ``` #### `BatchDeleteRequest` ```go type BatchDeleteRequest struct { TrackIDs []string `json:"track_ids" binding:"required" validate:"required,min=1,dive,uuid"` } ``` --- ## ✅ VALIDATION ### Compilation ```bash go build ./... ``` **Résultat** : ✅ **Compilation réussie** ### Tests unitaires ```bash go test ./internal/common -run TestBindAndValidateJSON -v ``` **Résultat** : ✅ **Tous les tests passent (8/8)** ### Tests complets ```bash go test ./... -count=1 ``` **Résultat** : Tests unitaires P1-002 passent. Les tests qui échouent sont préexistants. --- ## 🎯 OBJECTIFS ATTEINTS - ✅ **Helper centralisé** : `common.BindAndValidateJSON()` utilisable par tous les handlers - ✅ **DTOs avec tags validate** : Tous les DTOs critiques ont des tags `validate` - ✅ **Pas de validation inline** : Tous les handlers utilisent le helper - ✅ **Format d'erreur standardisé** : HTTP 400 avec détails structurés - ✅ **Tests complets** : Cas invalides, limites, UUID invalides, body trop gros --- ## 📋 COMMANDES DE VALIDATION ### Tests unitaires ```bash go test ./internal/common -run TestBindAndValidateJSON -v -count=1 ``` ### Tests complets ```bash go test ./... -count=1 ``` ### Compilation ```bash go build ./... ``` --- **Statut final** : ✅ **P1-002 IMPLÉMENTÉ ET VALIDÉ**