Aller au contenu

Feature 03 — Chien (Gestion des Exceptions)

Nous allons mettre en place la partie de gestion des Exceptions pour le Chien. Elle pourra vous servir de modèle pour les autres entités et contrôleurs.

Reprenons le code de base de notre contrôleur : ChienController

@RestController
@RequestMapping("/api/chiens")
public class ChienController {

    private final ChienService chienService;

    public ChienController(ChienService chienService) {
        this.chienService = chienService;
    }

    // Récupérer tous les chiens : GET /api/chiens
    @GetMapping
    public ResponseEntity<List<ChienDto>> getAllChiens() {
        List<ChienDto> chiens = chienService.getAllChiens();
        return ResponseEntity.ok(chiens);
    }

    // Créer un chien : POST /api/chiens
    @PostMapping
    public ResponseEntity<ChienDto> createChien(@Valid @RequestBody ChienCreateDto createDto) {
        ChienDto savedChien = chienService.createChien(createDto);
        return ResponseEntity.ok(savedChien);
    }

    // Récupérer un chien par ID : GET /api/chiens/{id}
    @GetMapping("/{id}")
    public ResponseEntity<ChienDto> getChien(@PathVariable Long id) {
        ChienDto chienDto = chienService.getChienById(id);
        return ResponseEntity.ok(chienDto);
    }
}

Gestion des exceptions

    @PostMapping
    public ResponseEntity<ChienDto> createChien(@Valid @RequestBody ChienCreateDto createDto) {
        ChienDto savedChien = chienService.createChien(createDto);
        return ResponseEntity.ok(savedChien);
    }

Dans ce type de code, les exceptions peuvent survenir à plusieurs niveaux :

Les Exceptions de VALIDATION

Spring gère automatiquement les erreurs de validation (grâce à @Valid) et renvoie une réponse 400 Bad Request si les contraintes ne sont pas respectées.

Exemple de réponse en cas d’erreur :

{
  "timestamp": "2023-10-01T12:00:00.000+00:00",
  "status": 400,
  "error": "Bad Request",
  "message": "Validation failed",
  "errors": [
    { "field": "nom", "message": "Le nom ne peut pas être vide" }
  ]
}

Les Exceptions METIER

Pour les erreurs métiers (doublon), on peut créer des exceptions personnalisées et les gérer avec @ExceptionHandler.

Exemple d’exception personnalisée :

public class ChienAlreadyExistsException extends RuntimeException {
    public ChienAlreadyExistsException(String message) {
        super(message);
    }
}

Exemple dans le service :

public ChienDto createChien(ChienCreateDto createDto) {
    if (chienRepository.existsByNumeroTatouage(createDto.numeroTatouage())) {
        throw new ChienAlreadyExistsException("Un chien avec le numéro de tatouage " + createDto.numeroTatouage() + " existe déjà.");
    }
    // logique de création si tout est ok
}

Gestion globale des exceptions

On crée une classe pour gérer les exceptions au niveau du contrôleur.

Comme on a redéfini le ErrorResponse, il faut penser à enlever l’importation par défaut de la classe ErrorResponse de Spring Framework.

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(ChienAlreadyExistsException.class)
    public ResponseEntity<ErrorResponse> handleChienAlreadyExists(ChienAlreadyExistsException ex) {
        ErrorResponse errorResponse = new ErrorResponse(
            HttpStatus.CONFLICT.value(),
            "Conflit",
            ex.getMessage(),
            LocalDateTime.now()
        );
        return new ResponseEntity<>(errorResponse, HttpStatus.CONFLICT);
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handleValidationExceptions(MethodArgumentNotValidException ex) {
        List<String> errors = ex.getBindingResult()
            .getFieldErrors()
            .stream()
            .map(error -> error.getField() + ": " + error.getDefaultMessage())
            .toList();

        ErrorResponse errorResponse = new ErrorResponse(
            HttpStatus.BAD_REQUEST.value(),
            "Validation échouée",
            "Erreurs de validation",
         //   errors,
            LocalDateTime.now()
        );
        return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
    }
}

Création d’une classe ou record “ErrorResponse”

Vous pouvez éventuellement ajouter un attribut detail : peut être une liste d’erreurs ou un message simple.

public record ErrorResponse(
    int status,
    String error,
    String message,
    LocalDateTime timestamp
) {}

Dans le Service

  public ChienDto createChien(ChienCreateDto createDto) {
        if (chienRepository.existsByNumeroTatouage(createDto.numeroTatouage())) {
            throw new ChienAlreadyExistsException(
                "Mince, un chien avec le numéro de tatouage " + createDto.numeroTatouage() + " existe déjà !"
            );
        }
    	// sinon si tout est ok, on poursuit (en tous cas si l'exception n'est pas levée)
        Chien chien = new Chien();
        chien.setNom(createDto.nom());
        chien.setNumeroTatouage(createDto.numeroTatouage());
        chien.setRace(raceRepository.findById(createDto.raceId()).orElseThrow(() -> new EntityNotFoundException("Race non trouvée avec l'ID : " + createDto.raceId())));
        chien.setProprietaire(adherentRepository.findById(createDto.proprietaireId()).orElseThrow(() -> new EntityNotFoundException("Adhérent non trouvé avec l'ID : " + createDto.proprietaireId())));

        Chien entityChien = chienRepository.save(chien);
        return chienMapper.toDto(entityChien);
    }

Dans le contrôleur

@PostMapping("/creer")
	 public ResponseEntity<ChienDto> createChien(@Valid @RequestBody ChienCreateDto dto)
	  {
	    return ResponseEntity.status(HttpStatus.CREATED).body(serviceChien.createChien(dto));
	  }

Réponse en cas de succès

Si tout se passe bien, la réponse sera : Statut HTTP : 201 Created

Corps :

{
  "id": 1,
  "nom": "Rex",
  "race": "Berger Allemand",
  "numeroTatouage": "TAT123",
  "proprietaire": "Aurora"
}

Réponse en cas d’erreur

Erreur de validation : Statut HTTP : 400 Bad Request

Corps :

{
  "status": 400,
  "error": "Validation échouée",
  "message": "Erreurs de validation",
  "details": [
    "nom: Le nom ne peut pas être vide",
    "numeroTatouage: Le numéro de tatouage est obligatoire !"
  ],
  "timestamp": "2023-10-01T12:00:00"
}

Chien déjà existant

Statut HTTP : 409 Conflict

Corps :

{
  "status": 409,
  "error": "Conflit",
  "message": "Un chien avec le numéro de tatouage TAT123 existe déjà.",
  "timestamp": "2023-10-01T12:00:00"
}

Points clés à retenir


A Faire