diff --git a/src/main/kotlin/fi/hsl/jore4/timetables/api/TimetablesController.kt b/src/main/kotlin/fi/hsl/jore4/timetables/api/TimetablesController.kt index b523fb85..ba0433bf 100644 --- a/src/main/kotlin/fi/hsl/jore4/timetables/api/TimetablesController.kt +++ b/src/main/kotlin/fi/hsl/jore4/timetables/api/TimetablesController.kt @@ -2,6 +2,12 @@ package fi.hsl.jore4.timetables.api import fi.hsl.jore4.timetables.api.util.HasuraErrorExtensions import fi.hsl.jore4.timetables.api.util.HasuraErrorResponse +import fi.hsl.jore4.timetables.api.util.InvalidTargetPriorityExtensions +import fi.hsl.jore4.timetables.api.util.MultipleTargetFramesFoundExtensions +import fi.hsl.jore4.timetables.api.util.PlainStatusExtensions +import fi.hsl.jore4.timetables.api.util.StagingVehicleScheduleFrameNotFoundExtensions +import fi.hsl.jore4.timetables.api.util.TargetPriorityParsingExtensions +import fi.hsl.jore4.timetables.api.util.TargetVehicleScheduleFrameNotFoundExtensions import fi.hsl.jore4.timetables.enumerated.TimetablesPriority import fi.hsl.jore4.timetables.service.CombineTimetablesService import fi.hsl.jore4.timetables.service.InvalidTargetPriorityException @@ -111,97 +117,51 @@ class TimetablesController( @ExceptionHandler(RuntimeException::class) fun handleRuntimeException(ex: RuntimeException): ResponseEntity { - LOGGER.error { "Exception during request:$ex" } - LOGGER.error(ex.stackTraceToString()) - - val httpStatus = HttpStatus.CONFLICT // Hasura only wants errors on 4xx range. - val hasuraErrorExtensions = HasuraErrorExtensions(httpStatus.value()) - val hasuraErrorResponse = HasuraErrorResponse(ex.message, hasuraErrorExtensions) - - return ResponseEntity(hasuraErrorResponse, httpStatus) - } - - class InvalidTargetPriorityExtensions( - override val code: Int, - val targetPriority: TimetablesPriority - ) : HasuraErrorExtensions(code) - - @ExceptionHandler(InvalidTargetPriorityException::class) - fun handleInvalidTargetPriorityException(ex: InvalidTargetPriorityException): ResponseEntity { - val httpStatus = HttpStatus.BAD_REQUEST - val hasuraErrorExtensions = InvalidTargetPriorityExtensions(httpStatus.value(), ex.targetPriority) - val hasuraErrorResponse = HasuraErrorResponse(ex.message, hasuraErrorExtensions) - - return ResponseEntity(hasuraErrorResponse, httpStatus) - } - - class StagingVehicleScheduleFrameNotFoundExtensions( - override val code: Int, - val stagingVehicleScheduleFrameId: UUID - ) : HasuraErrorExtensions(code) - - @ExceptionHandler(StagingVehicleScheduleFrameNotFoundException::class) - fun handleStagingVehicleScheduleFrameNotFoundException(ex: StagingVehicleScheduleFrameNotFoundException): ResponseEntity { - val httpStatus = HttpStatus.NOT_FOUND - val hasuraErrorExtensions = StagingVehicleScheduleFrameNotFoundExtensions( - httpStatus.value(), - ex.stagingVehicleScheduleFrameId - ) - val hasuraErrorResponse = HasuraErrorResponse(ex.message, hasuraErrorExtensions) - - return ResponseEntity(hasuraErrorResponse, httpStatus) - } - - class TargetVehicleScheduleFrameNotFoundExtensions( - override val code: Int, - val stagingVehicleScheduleFrameId: UUID - ) : HasuraErrorExtensions(code) - - @ExceptionHandler(TargetFrameNotFoundException::class) - fun handleTargetFrameNotFoundException(ex: TargetFrameNotFoundException): ResponseEntity { - val httpStatus = HttpStatus.NOT_FOUND - val hasuraErrorExtensions = TargetVehicleScheduleFrameNotFoundExtensions( - httpStatus.value(), - ex.stagingVehicleScheduleFrameId - ) - val hasuraErrorResponse = HasuraErrorResponse(ex.message, hasuraErrorExtensions) - - return ResponseEntity(hasuraErrorResponse, httpStatus) - } - - class MultipleTargetFramesFoundExtensions( - override val code: Int, - val stagingVehicleScheduleFrameId: UUID, - val targetVehicleScheduleFrameIds: List - ) : HasuraErrorExtensions(code) - - @ExceptionHandler(MultipleTargetFramesFoundException::class) - fun handleTargetFrameNotFoundException(ex: MultipleTargetFramesFoundException): ResponseEntity { - val httpStatus = HttpStatus.CONFLICT - val hasuraErrorExtensions = MultipleTargetFramesFoundExtensions( - httpStatus.value(), - ex.stagingVehicleScheduleFrameId, - ex.targetVehicleScheduleFrameIds - ) - val hasuraErrorResponse = HasuraErrorResponse(ex.message, hasuraErrorExtensions) + val hasuraExtensions: HasuraErrorExtensions = when (ex) { + is InvalidTargetPriorityException -> { + InvalidTargetPriorityExtensions(HttpStatus.BAD_REQUEST, ex.targetPriority) + } + + is StagingVehicleScheduleFrameNotFoundException -> { + StagingVehicleScheduleFrameNotFoundExtensions(HttpStatus.NOT_FOUND, ex.stagingVehicleScheduleFrameId) + } + + is TargetFrameNotFoundException -> { + TargetVehicleScheduleFrameNotFoundExtensions(HttpStatus.NOT_FOUND, ex.stagingVehicleScheduleFrameId) + } + + is MultipleTargetFramesFoundException -> { + MultipleTargetFramesFoundExtensions( + HttpStatus.CONFLICT, + ex.stagingVehicleScheduleFrameId, + ex.targetVehicleScheduleFrameIds + ) + } + + is TargetPriorityParsingException -> { + TargetPriorityParsingExtensions(HttpStatus.BAD_REQUEST, ex.targetPriority) + } + + else -> { + LOGGER.error { "Exception during request:$ex" } + LOGGER.error(ex.stackTraceToString()) + + PlainStatusExtensions(HttpStatus.CONFLICT) + } + } - return ResponseEntity(hasuraErrorResponse, httpStatus) - } + val httpStatus: HttpStatus = hasuraExtensions.run { + if (code !in 400..499) { + LOGGER.warn { "Violating Hasura error response contract by returning code not like 4xx: $code" } + } - class TargetPriorityParsingExtensions( - override val code: Int, - val targetPriority: Int - ) : HasuraErrorExtensions(code) - - @ExceptionHandler(TargetPriorityParsingException::class) - fun handleIncompatibleTargetPriorityException(ex: TargetPriorityParsingException): ResponseEntity { - val httpStatus = HttpStatus.BAD_REQUEST - val hasuraErrorExtensions = TargetPriorityParsingExtensions( - httpStatus.value(), - ex.targetPriority - ) - val hasuraErrorResponse = HasuraErrorResponse(ex.message, hasuraErrorExtensions) + HttpStatus.resolve(code) ?: run { + // This block should never be entered (and never will when using valid HTTP status codes). + LOGGER.warn { "Could not resolve HttpStatus from code $code" } + HttpStatus.BAD_REQUEST // default in case not resolved + } + } - return ResponseEntity(hasuraErrorResponse, httpStatus) + return ResponseEntity(HasuraErrorResponse(ex.message, hasuraExtensions), httpStatus) } } diff --git a/src/main/kotlin/fi/hsl/jore4/timetables/api/util/HasuraErrorExtensions.kt b/src/main/kotlin/fi/hsl/jore4/timetables/api/util/HasuraErrorExtensions.kt new file mode 100644 index 00000000..d476e5db --- /dev/null +++ b/src/main/kotlin/fi/hsl/jore4/timetables/api/util/HasuraErrorExtensions.kt @@ -0,0 +1,38 @@ +package fi.hsl.jore4.timetables.api.util + +import fi.hsl.jore4.timetables.enumerated.TimetablesPriority +import org.springframework.http.HttpStatus +import java.util.UUID + +sealed class HasuraErrorExtensions(httpStatus: HttpStatus) { + // code must be 4xx + val code: Int = httpStatus.value() +} + +class PlainStatusExtensions(httpStatus: HttpStatus) : HasuraErrorExtensions(httpStatus) + +class InvalidTargetPriorityExtensions( + httpStatus: HttpStatus, + val targetPriority: TimetablesPriority +) : HasuraErrorExtensions(httpStatus) + +class StagingVehicleScheduleFrameNotFoundExtensions( + httpStatus: HttpStatus, + val stagingVehicleScheduleFrameId: UUID +) : HasuraErrorExtensions(httpStatus) + +class TargetVehicleScheduleFrameNotFoundExtensions( + httpStatus: HttpStatus, + val stagingVehicleScheduleFrameId: UUID +) : HasuraErrorExtensions(httpStatus) + +class MultipleTargetFramesFoundExtensions( + httpStatus: HttpStatus, + val stagingVehicleScheduleFrameId: UUID, + val targetVehicleScheduleFrameIds: List +) : HasuraErrorExtensions(httpStatus) + +class TargetPriorityParsingExtensions( + httpStatus: HttpStatus, + val targetPriority: Int +) : HasuraErrorExtensions(httpStatus) diff --git a/src/main/kotlin/fi/hsl/jore4/timetables/api/util/HasuraErrorResponse.kt b/src/main/kotlin/fi/hsl/jore4/timetables/api/util/HasuraErrorResponse.kt index d93d017d..4e3db89c 100644 --- a/src/main/kotlin/fi/hsl/jore4/timetables/api/util/HasuraErrorResponse.kt +++ b/src/main/kotlin/fi/hsl/jore4/timetables/api/util/HasuraErrorResponse.kt @@ -1,13 +1,9 @@ package fi.hsl.jore4.timetables.api.util // See https://hasura.io/docs/latest/actions/action-handlers/#returning-an-error-response -open class HasuraErrorResponse( +class HasuraErrorResponse( nullableMessage: String?, val extensions: HasuraErrorExtensions ) { val message = nullableMessage ?: "An error occurred." } - -open class HasuraErrorExtensions( - open val code: Int // Must be a 4xx code -)