Skip to content

Commit

Permalink
Ensure api error responses always contain messages as well as HTTP st…
Browse files Browse the repository at this point in the history
…atus codes
  • Loading branch information
ushkarev committed Oct 8, 2023
1 parent 0dde466 commit 694fc4c
Show file tree
Hide file tree
Showing 2 changed files with 21 additions and 14 deletions.
16 changes: 11 additions & 5 deletions clients/node/src/errorTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
export interface ErrorResponse {
status: number

errorCode?: ErrorCode
userMessage: string

userMessage?: string
developerMessage: string

developerMessage?: string
errorCode?: ErrorCode

moreInfo?: string
}
Expand All @@ -18,8 +18,14 @@ export interface ErrorResponse {
* Used to determine if an api error was of type `ErrorResponse` to use relevant properties
*/
export function isErrorResponse(obj: unknown): obj is ErrorResponse {
// TODO: would be nice to make userMessage & developerMessage non-nullable in the api
return Boolean(obj && typeof obj === 'object' && 'status' in obj && typeof obj.status === 'number')
return Boolean(
obj &&
typeof obj === 'object' &&
'status' in obj &&
typeof obj.status === 'number' &&
'userMessage' in obj &&
'developerMessage' in obj,
)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,13 +132,14 @@ class HmppsNonAssociationsApiExceptionHandler {
@ExceptionHandler(ResponseStatusException::class)
fun handleResponseStatusException(e: ResponseStatusException): ResponseEntity<ErrorResponse?>? {
log.debug("Response status exception caught: {}", e.message)
val reason = e.reason ?: "Unknown error"
return ResponseEntity
.status(e.statusCode)
.body(
ErrorResponse(
status = e.statusCode.value(),
userMessage = e.reason,
developerMessage = e.reason,
userMessage = reason,
developerMessage = reason,
),
)
}
Expand Down Expand Up @@ -240,23 +241,23 @@ enum class ErrorCode(val errorCode: Int) {
data class ErrorResponse(
@Schema(description = "HTTP status code", example = "500", required = true)
val status: Int,
@Schema(description = "User message for the error", example = "No non-association found for ID `324234`", required = true)
val userMessage: String,
@Schema(description = "More detailed error message", example = "[Details, sometimes a stack trace]", required = true)
val developerMessage: String,
@Schema(description = "When present, uniquely identifies the type of error making it easier for clients to discriminate without relying on error description or HTTP status code; see `uk.gov.justice.digital.hmpps.hmppsnonassociationsapi.config.ErrorCode` enumeration in hmpps-non-associations-api", example = "101", required = false)
val errorCode: Int? = null,
@Schema(description = "User message for the error", example = "No non-association found for ID `324234`", required = false)
val userMessage: String? = null,
@Schema(description = "More detailed error message", example = "[Details, sometimes a stack trace]", required = false)
val developerMessage: String? = null,
@Schema(description = "More information about the error", example = "[Rarely used, error-specific]", required = false)
val moreInfo: String? = null,
) {
constructor(
status: HttpStatus,
errorCode: ErrorCode? = null,
userMessage: String? = null,
userMessage: String,
developerMessage: String? = null,
errorCode: ErrorCode? = null,
moreInfo: String? = null,
) :
this(status.value(), errorCode?.errorCode, userMessage, developerMessage, moreInfo)
this(status.value(), userMessage, developerMessage ?: userMessage, errorCode?.errorCode, moreInfo)
}

class NonAssociationAlreadyClosedException(id: Long) : Exception("Non-association [ID=$id] already closed")
Expand Down

0 comments on commit 694fc4c

Please sign in to comment.