video-surveillance-app/src/main/java/me/belkacemi/iotalarm/presentation/advice/GlobalExceptionHandler.java

80 lines
3.4 KiB
Java
Raw Normal View History

package me.belkacemi.iotalarm.presentation.advice;
import me.belkacemi.iotalarm.domain.exception.DomainException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ProblemDetail;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.net.URI;
import java.util.stream.Collectors;
/**
* Traduit les exceptions en reponses HTTP, au format RFC 7807 (ProblemDetail).
*
* <p>C'est ici, et nulle part ailleurs, que le domaine rencontre HTTP. Une
* {@link DomainException} ne sait pas ce qu'est un code 409 - et c'est
* precisement ce qui permet de la lever depuis l'agregat sans le coupler au web.</p>
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* Regle metier violee : armer une alarme deja armee, acquitter deux fois.
* 409 et non 400 - la requete est bien formee, c'est l'etat qui s'y oppose.
*/
@ExceptionHandler(DomainException.class)
public ProblemDetail onDomainException(DomainException exception) {
log.info("Regle metier refusee : {}", exception.getMessage());
ProblemDetail problem = ProblemDetail.forStatusAndDetail(
HttpStatus.CONFLICT, exception.getMessage());
problem.setTitle("Regle metier violee");
problem.setType(URI.create("urn:iot-alarm:domain-rule-violation"));
return problem;
}
/** Donnee invalide a la frontiere : type de capteur inconnu, deviceId malforme. */
@ExceptionHandler(IllegalArgumentException.class)
public ProblemDetail onIllegalArgument(IllegalArgumentException exception) {
log.info("Requete rejetee : {}", exception.getMessage());
ProblemDetail problem = ProblemDetail.forStatusAndDetail(
HttpStatus.BAD_REQUEST, exception.getMessage());
problem.setTitle("Requete invalide");
problem.setType(URI.create("urn:iot-alarm:invalid-request"));
return problem;
}
/** Echec de Bean Validation : champs manquants ou hors bornes. */
@ExceptionHandler(MethodArgumentNotValidException.class)
public ProblemDetail onValidationError(MethodArgumentNotValidException exception) {
String details = exception.getBindingResult().getFieldErrors().stream()
.map(error -> error.getField() + " : " + error.getDefaultMessage())
.collect(Collectors.joining(", "));
ProblemDetail problem = ProblemDetail.forStatusAndDetail(
HttpStatus.BAD_REQUEST, details);
problem.setTitle("Validation echouee");
problem.setType(URI.create("urn:iot-alarm:validation-failed"));
return problem;
}
/**
* Filet de securite. On journalise la stacktrace mais on ne la renvoie pas :
* l'endpoint d'ingestion est expose publiquement.
*/
@ExceptionHandler(Exception.class)
public ProblemDetail onUnexpectedError(Exception exception) {
log.error("Erreur inattendue", exception);
ProblemDetail problem = ProblemDetail.forStatusAndDetail(
HttpStatus.INTERNAL_SERVER_ERROR,
"Une erreur interne est survenue.");
problem.setTitle("Erreur interne");
return problem;
}
}