diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/entities/ErrorCode.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/entities/ErrorCode.kt deleted file mode 100644 index 03cdb8c2c..000000000 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/entities/ErrorCode.kt +++ /dev/null @@ -1,16 +0,0 @@ -package fr.gouv.cacem.monitorenv.domain.entities - -// Don't forget to mirror any update here in the frontend enum. -enum class ErrorCode { - /** - * Thrown when attempting to attach a mission to a reporting that has already a mission - * attached. - */ - CHILD_ALREADY_ATTACHED, - - /** Thrown when attempting to archive an entity linked to non-archived child(ren). */ - UNARCHIVED_CHILD, - - /** Thrown when attempting to delete a mission that has actions created by other applications. */ - EXISTING_MISSION_ACTION, -} diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/BackendInternalException.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/BackendInternalException.kt new file mode 100644 index 000000000..3cf728a94 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/BackendInternalException.kt @@ -0,0 +1,25 @@ +package fr.gouv.cacem.monitorenv.domain.exceptions + +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +/** + * Domain exception to throw when an internal error occurred in the backend. + * + * ## Examples + * - An unexpected exception has been caught. + * + * ## Logging + * This exception is logged as an error on the Backend side. + */ +open class BackendInternalException( + final override val message: String, + val originalException: Exception? = null, +) : Throwable(message) { + private val logger: Logger = LoggerFactory.getLogger(BackendInternalException::class.java) + + init { + logger.error("BackendInternalException: $message") + originalException?.let { logger.error("${it::class.simpleName}: ${it.message}") } + } +} diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/BackendRequestErrorCode.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/BackendRequestErrorCode.kt new file mode 100644 index 000000000..7a0b4c0f2 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/BackendRequestErrorCode.kt @@ -0,0 +1,20 @@ +package fr.gouv.cacem.monitorenv.domain.exceptions + +/** + * Error code thrown when the request is invalid. + * + * ## Examples + * - The request has missing or wrongly-typed properties. + * - The request has valid properties but is made of illogical data. + * + * ## Logging + * The related exception is logged as a warning on the Backend side in order to track any unexpected behavior. + * It should definitely be logged on the Frontend side as an error. + * + * ## ⚠️ Important + * **Don't forget to mirror any update here in the corresponding Frontend enum.** + */ +enum class BackendRequestErrorCode { + /** Thrown when a request body property has an unexpected type. */ + WRONG_REQUEST_BODY_PROPERTY_TYPE, +} diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/BackendRequestException.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/BackendRequestException.kt new file mode 100644 index 000000000..8a01de002 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/BackendRequestException.kt @@ -0,0 +1,30 @@ +package fr.gouv.cacem.monitorenv.domain.exceptions + +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +/** + * Domain exception to throw when a request is invalid. + * + * ## Examples + * - The request has missing or wrongly-typed properties. + * - The request has valid properties but is made of illogical data. + * + * ## Logging + * This exception is logged as a warning on the Backend side in order to track any unexpected behavior. + * It should definitely be logged on the Frontend side as an error. + * + * ## ⚠️ Important + * **Don't forget to mirror any update here in the corresponding Frontend enum.** + */ +open class BackendRequestException( + val code: BackendRequestErrorCode, + final override val message: String? = null, + val data: Any? = null, +) : Throwable(code.name) { + private val logger: Logger = LoggerFactory.getLogger(BackendRequestException::class.java) + + init { + logger.warn("$code: ${message ?: "No message."}") + } +} diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/BackendUsageErrorCode.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/BackendUsageErrorCode.kt new file mode 100644 index 000000000..a8301b4b7 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/BackendUsageErrorCode.kt @@ -0,0 +1,33 @@ +package fr.gouv.cacem.monitorenv.domain.exceptions + +/** + * Error code thrown when the request is valid but the backend cannot process it. + * + * It's called "usage" because this request likely comes from an end-user action that's no longer valid + * which happens when their client data is not up-to-date with the backend. + * + * ## Examples + * - A user tries to create a resource that has already been created. + * - A user tries to delete a resource that doesn't exist anymore. + * + * ## Logging + * The related exception is NOT logged on the Backend side. + * It should NOT be logged on the Frontend side, + * it should rather display a comprehensible error message to the end-user. + * + * ### ⚠️ Important + * **Don't forget to mirror any update here in the corresponding Frontend enum.** + */ +enum class BackendUsageErrorCode { + /** + * Thrown when attempting to attach a mission to a reporting that has already a mission + * attached. + */ + CHILD_ALREADY_ATTACHED, + + /** Thrown when attempting to archive an entity linked to non-archived child(ren). */ + UNARCHIVED_CHILD, + + /** Thrown when attempting to delete a mission that has actions created by other applications. */ + EXISTING_MISSION_ACTION, +} diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/BackendUsageException.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/BackendUsageException.kt index 473d03219..7e7d08565 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/BackendUsageException.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/BackendUsageException.kt @@ -1,5 +1,22 @@ package fr.gouv.cacem.monitorenv.domain.exceptions -import fr.gouv.cacem.monitorenv.domain.entities.ErrorCode - -class BackendUsageException(val code: ErrorCode, val data: Any) : Throwable(code.name) +/** + * Domain exception to throw when a request is valid but the backend cannot process it. + * + * It's called "usage" because this request likely comes from an end-user action that's no longer valid + * which happens when their client data is not up-to-date with the backend. + * + * ## Examples + * - A user tries to create a resource that has already been created. + * - A user tries to delete a resource that doesn't exist anymore. + * + * ## Logging + * This exception is NOT logged on the Backend side. + * It should NOT be logged on the Frontend side, + * it should rather display a comprehensible error message to the end-user. + */ +open class BackendUsageException( + val code: BackendUsageErrorCode, + final override val message: String? = null, + val data: Any? = null, +) : Throwable(code.name) diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/CodeNotFoundException.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/CodeNotFoundException.kt deleted file mode 100644 index aecfcbda8..000000000 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/CodeNotFoundException.kt +++ /dev/null @@ -1,4 +0,0 @@ -package fr.gouv.cacem.monitorenv.domain.exceptions - -class CodeNotFoundException(message: String, cause: Throwable? = null) : - Throwable(message, cause) diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/CouldNotArchiveException.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/CouldNotArchiveException.kt index b759b54c7..7b50c5e6d 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/CouldNotArchiveException.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/CouldNotArchiveException.kt @@ -1,3 +1,4 @@ package fr.gouv.cacem.monitorenv.domain.exceptions +@Deprecated("Use `BackendUsageException` instead.") class CouldNotArchiveException(message: String) : RuntimeException(message) diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/CouldNotDeleteException.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/CouldNotDeleteException.kt index 6d5c9ef2d..d05bcfb15 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/CouldNotDeleteException.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/CouldNotDeleteException.kt @@ -1,3 +1,4 @@ package fr.gouv.cacem.monitorenv.domain.exceptions +@Deprecated("Use `BackendUsageException` instead.") class CouldNotDeleteException(message: String) : RuntimeException(message) diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/CouldNotUpdateMissionException.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/CouldNotUpdateMissionException.kt deleted file mode 100644 index 503b6c4f5..000000000 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/CouldNotUpdateMissionException.kt +++ /dev/null @@ -1,4 +0,0 @@ -package fr.gouv.cacem.monitorenv.domain.exceptions - -class CouldNotUpdateMissionException(message: String, cause: Throwable? = null) : - Throwable(message, cause) diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/EntityConversionException.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/EntityConversionException.kt index aa666d45f..e2a782c0c 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/EntityConversionException.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/EntityConversionException.kt @@ -1,4 +1,5 @@ package fr.gouv.cacem.monitorenv.domain.exceptions +@Deprecated("Use `BackendInternalException` or `BackendRequestException` instead (depending on the case).") class EntityConversionException(message: String, cause: Throwable? = null) : Throwable(message, cause) diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/NotFoundException.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/NotFoundException.kt index 5e29386f5..2cfcc770b 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/NotFoundException.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/NotFoundException.kt @@ -1,4 +1,5 @@ package fr.gouv.cacem.monitorenv.domain.exceptions +@Deprecated("Use `BackendUsageException` instead.") class NotFoundException(message: String, cause: Throwable? = null) : Throwable(message, cause) diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/ReportingAlreadyAttachedException.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/ReportingAlreadyAttachedException.kt index 86a5cc4ac..c77c65bbe 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/ReportingAlreadyAttachedException.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/exceptions/ReportingAlreadyAttachedException.kt @@ -1,4 +1,5 @@ package fr.gouv.cacem.monitorenv.domain.exceptions /** Thrown when attempting to attach a reporting already attached to a mission. */ +@Deprecated("Use `BackendUsageException` instead.") class ReportingAlreadyAttachedException(message: String) : RuntimeException(message) diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/DeleteMission.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/DeleteMission.kt index f37b2b7fa..956906f4a 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/DeleteMission.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/DeleteMission.kt @@ -3,14 +3,14 @@ package fr.gouv.cacem.monitorenv.domain.use_cases.missions import fr.gouv.cacem.monitorenv.config.UseCase -import fr.gouv.cacem.monitorenv.domain.entities.ErrorCode import fr.gouv.cacem.monitorenv.domain.entities.mission.MissionSourceEnum +import fr.gouv.cacem.monitorenv.domain.exceptions.BackendUsageErrorCode import fr.gouv.cacem.monitorenv.domain.exceptions.BackendUsageException import fr.gouv.cacem.monitorenv.domain.repositories.IMissionRepository import fr.gouv.cacem.monitorenv.domain.repositories.IReportingRepository import org.slf4j.LoggerFactory import java.time.ZonedDateTime -import java.util.UUID +import java.util.* @UseCase class DeleteMission( @@ -40,8 +40,8 @@ class DeleteMission( } throw BackendUsageException( - ErrorCode.EXISTING_MISSION_ACTION, - errorSources, + code = BackendUsageErrorCode.EXISTING_MISSION_ACTION, + data = errorSources, ) } diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/ApiError.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/ApiError.kt index d3a763ab1..a65d407c4 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/ApiError.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/ApiError.kt @@ -1,5 +1,8 @@ package fr.gouv.cacem.monitorenv.infrastructure.api.adapters.bff.outputs +@Deprecated( + "Use either `BackendInternalErrorDataOutput`, `BackendRequestErrorDataOutput` or `BackendUsageErrorDataOutput` instead.", +) class ApiError(val type: String) { constructor(exception: Throwable) : this(exception.cause?.javaClass?.simpleName.toString()) } diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/BackendInternalErrorDataOutput.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/BackendInternalErrorDataOutput.kt new file mode 100644 index 000000000..0cc635d21 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/BackendInternalErrorDataOutput.kt @@ -0,0 +1,14 @@ +package fr.gouv.cacem.monitorenv.infrastructure.api.adapters.bff.outputs + +/** + * Domain exception to throw when an internal error occurred in the backend. + * + * ## Examples + * - An unexpected exception has been caught. + * + * ## Logging + * The related exception is logged as an error on the Backend side. + */ +class BackendInternalErrorDataOutput { + val message: String = "An internal error occurred." +} diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/BackendRequestErrorDataOutput.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/BackendRequestErrorDataOutput.kt new file mode 100644 index 000000000..f28ec8c5f --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/BackendRequestErrorDataOutput.kt @@ -0,0 +1,20 @@ +package fr.gouv.cacem.monitorenv.infrastructure.api.adapters.bff.outputs + +import fr.gouv.cacem.monitorenv.domain.exceptions.BackendRequestErrorCode + +/** + * Error output to use when the request is invalid. + * + * ## Examples + * - The request has missing or wrongly-typed properties. + * - The request has valid properties but is made of illogical data. + * + * ## Logging + * The related exception is logged as a warning on the Backend side in order to track any unexpected behavior. + * It should definitely be logged on the Frontend side as an error. + */ +data class BackendRequestErrorDataOutput( + val code: BackendRequestErrorCode, + val data: Any? = null, + val message: String? = null, +) diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/BackendUsageError.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/BackendUsageError.kt deleted file mode 100644 index 2e9c639c9..000000000 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/BackendUsageError.kt +++ /dev/null @@ -1,5 +0,0 @@ -package fr.gouv.cacem.monitorenv.infrastructure.api.adapters.bff.outputs - -import fr.gouv.cacem.monitorenv.domain.entities.ErrorCode - -class BackendUsageError(val code: ErrorCode, val data: Any) diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/BackendUsageErrorDataOutput.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/BackendUsageErrorDataOutput.kt new file mode 100644 index 000000000..5f67ddda5 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/BackendUsageErrorDataOutput.kt @@ -0,0 +1,24 @@ +package fr.gouv.cacem.monitorenv.infrastructure.api.adapters.bff.outputs + +import fr.gouv.cacem.monitorenv.domain.exceptions.BackendUsageErrorCode + +/** + * Error output to use when the request is valid but the backend cannot process it. + * + * It's called "usage" because this request likely comes from an end-user action that's no longer valid + * which happens when their client data is not up-to-date with the backend. + * + * ## Examples + * - A user tries to create a resource that has already been created. + * - A user tries to delete a resource that doesn't exist anymore. + * + * ## Logging + * The related exception is NOT logged on the Backend side. + * It should NOT be logged on the Frontend side, + * it should rather display a comprehensible error message to the end-user. + */ +data class BackendUsageErrorDataOutput( + val code: BackendUsageErrorCode, + val data: Any? = null, + val message: String? = null, +) diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/ControllersExceptionHandler.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/ControllersExceptionHandler.kt index fff145bae..5b1dccd3f 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/ControllersExceptionHandler.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/ControllersExceptionHandler.kt @@ -1,11 +1,7 @@ package fr.gouv.cacem.monitorenv.infrastructure.api.endpoints -import fr.gouv.cacem.monitorenv.domain.entities.ErrorCode -import fr.gouv.cacem.monitorenv.domain.exceptions.BackendUsageException -import fr.gouv.cacem.monitorenv.domain.exceptions.ReportingAlreadyAttachedException -import fr.gouv.cacem.monitorenv.infrastructure.api.adapters.bff.outputs.ApiError -import fr.gouv.cacem.monitorenv.infrastructure.api.adapters.bff.outputs.BackendUsageError -import fr.gouv.cacem.monitorenv.infrastructure.api.adapters.bff.outputs.MissingParameterApiError +import fr.gouv.cacem.monitorenv.domain.exceptions.* +import fr.gouv.cacem.monitorenv.infrastructure.api.adapters.bff.outputs.* import fr.gouv.cacem.monitorenv.infrastructure.database.repositories.exceptions.UnarchivedChildException import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -20,53 +16,101 @@ import org.springframework.web.bind.annotation.RestControllerAdvice @RestControllerAdvice @Order(HIGHEST_PRECEDENCE) -class ControllersExceptionHandler() { +class ControllersExceptionHandler { private val logger: Logger = LoggerFactory.getLogger(ControllersExceptionHandler::class.java) + // ------------------------------------------------------------------------- + // Domain exceptions + + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + @ExceptionHandler(BackendInternalException::class) + fun handleBackendInternalException(e: BackendInternalException): BackendInternalErrorDataOutput { + return BackendInternalErrorDataOutput() + } + + @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY) + @ExceptionHandler(BackendRequestException::class) + fun handleBackendRequestException(e: BackendRequestException): BackendRequestErrorDataOutput { + return BackendRequestErrorDataOutput(code = e.code, data = e.data, message = null) + } + + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(BackendUsageException::class) + fun handleBackendUsageException(e: BackendUsageException): BackendUsageErrorDataOutput { + return BackendUsageErrorDataOutput(code = e.code, data = e.data, message = null) + } + + // ------------------------------------------------------------------------- + // Legacy exceptions + + // TODO Migrate to new error handling logic. @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(IllegalArgumentException::class) fun handleIllegalArgumentException(e: Exception): ApiError { logger.error(e.message, e) + return ApiError(IllegalArgumentException(e.message.toString(), e)) } + // TODO Migrate to new error handling logic. + // Which cases does this exception cover? @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(NoSuchElementException::class) fun handleNoSuchElementException(e: Exception): ApiError { logger.error(e.message, e) + return ApiError(NoSuchElementException(e.message.toString(), e)) } + // TODO Migrate to new error handling logic. @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(MissingServletRequestParameterException::class) fun handleNoParameter(e: MissingServletRequestParameterException): MissingParameterApiError { logger.error(e.message, e) + return MissingParameterApiError("Parameter \"${e.parameterName}\" is missing.") } + // TODO Migrate to new error handling logic. @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(HttpMessageNotReadableException::class) fun handleNoParameter(e: HttpMessageNotReadableException): ApiError { logger.error(e.message, e) + return ApiError(e) } + // TODO Migrate to new error handling logic. @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(UnarchivedChildException::class) fun handleUnarchivedChildException(e: UnarchivedChildException): ApiError { logger.error(e.message, e) - return ApiError(ErrorCode.UNARCHIVED_CHILD.name) + + return ApiError(BackendUsageErrorCode.UNARCHIVED_CHILD.name) } + // TODO Migrate to new error handling logic. @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(ReportingAlreadyAttachedException::class) fun handleReportingAlreadyAttachedToAMission(e: ReportingAlreadyAttachedException): ApiError { - return ApiError(ErrorCode.CHILD_ALREADY_ATTACHED.name) + return ApiError(BackendUsageErrorCode.CHILD_ALREADY_ATTACHED.name) } - @ResponseStatus(HttpStatus.BAD_REQUEST) - @ExceptionHandler(BackendUsageException::class) - fun handleBackendUsageError(e: BackendUsageException): BackendUsageError { - return BackendUsageError(code = e.code, data = e.data) + // ------------------------------------------------------------------------- + // Infrastructure and unhandled domain exceptions + // - Unhandled domain exceptions are a bug, thus an unexpected exception. + // - Infrastructure exceptions are not supposed to bubble up until here. + // They should be caught or transformed into domain exceptions. + // If that happens, it's a bug, thus an unexpected exception. + + /** + * Catch-all for unexpected exceptions. + */ + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + @ExceptionHandler(Exception::class) + fun handleUnexpectedException(e: Exception): BackendInternalErrorDataOutput { + logger.error(e.message, e) + + return BackendInternalErrorDataOutput() } } diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/exceptions/InfrastructureErrorCode.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/exceptions/InfrastructureErrorCode.kt new file mode 100644 index 000000000..2a3a877aa --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/exceptions/InfrastructureErrorCode.kt @@ -0,0 +1,3 @@ +package fr.gouv.cacem.monitorenv.infrastructure.database.repositories.exceptions + +enum class InfrastructureErrorCode diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/exceptions/InfrastructureException.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/exceptions/InfrastructureException.kt new file mode 100644 index 000000000..d5319988d --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/exceptions/InfrastructureException.kt @@ -0,0 +1,8 @@ +package fr.gouv.cacem.monitorenv.infrastructure.database.repositories.exceptions + +/** + * Exception to throw when any error occurs in the infrastructure layer. + * + * This exception should be caught and handled in the domain layer, likely by a use case. + */ +class InfrastructureException(code: InfrastructureErrorCode, message: String) : RuntimeException(message) diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/exceptions/UnarchivedChildException.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/exceptions/UnarchivedChildException.kt index 54d84e739..e67b8b525 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/exceptions/UnarchivedChildException.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/exceptions/UnarchivedChildException.kt @@ -1,4 +1,5 @@ package fr.gouv.cacem.monitorenv.infrastructure.database.repositories.exceptions /** Thrown when attempting to archive an entity linked to non-archived child(ren). */ +@Deprecated("Use `InfrastructureException` instead.") class UnarchivedChildException(message: String) : RuntimeException(message) diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/DeleteMissionUTests.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/DeleteMissionUTests.kt index e9d626a4d..bf880065a 100644 --- a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/DeleteMissionUTests.kt +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/DeleteMissionUTests.kt @@ -1,13 +1,15 @@ package fr.gouv.cacem.monitorenv.domain.use_cases.missions -import com.nhaarman.mockitokotlin2.* -import fr.gouv.cacem.monitorenv.domain.entities.ErrorCode +import com.nhaarman.mockitokotlin2.argumentCaptor +import com.nhaarman.mockitokotlin2.given +import com.nhaarman.mockitokotlin2.verify import fr.gouv.cacem.monitorenv.domain.entities.mission.CanDeleteMissionResponse import fr.gouv.cacem.monitorenv.domain.entities.mission.MissionEntity import fr.gouv.cacem.monitorenv.domain.entities.mission.MissionSourceEnum import fr.gouv.cacem.monitorenv.domain.entities.mission.MissionTypeEnum import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.envActionControl.EnvActionControlEntity import fr.gouv.cacem.monitorenv.domain.entities.reporting.ReportingEntity +import fr.gouv.cacem.monitorenv.domain.exceptions.BackendUsageErrorCode import fr.gouv.cacem.monitorenv.domain.exceptions.BackendUsageException import fr.gouv.cacem.monitorenv.domain.repositories.IMissionRepository import fr.gouv.cacem.monitorenv.domain.repositories.IReportingRepository @@ -145,7 +147,6 @@ class DeleteMissionUTests { mission = missionToDelete, attachedReportingIds = null, ), - ) val throwable = Assertions.catchThrowable { @@ -161,8 +162,8 @@ class DeleteMissionUTests { } Assertions.assertThat(throwable).isInstanceOf( BackendUsageException( - ErrorCode.EXISTING_MISSION_ACTION, - errorSources, + code = BackendUsageErrorCode.EXISTING_MISSION_ACTION, + data = errorSources, )::class.java, ) }