/** * Produces a generic error response. Call this if you know the error is a non-normal unhandled error of the "how * did we get here, this should never happen" variety, or if other attempts to deal with the error failed and you * need a guaranteed fallback that will produce a generic error response that follows our error contract. If you * have an error that happened during normal processing you should try {@link #processError(HttpProcessingState, * Object, Throwable)} instead in order to get an error response that is better tailored to the given error rather * than this one which guarantees a somewhat unhelpful generic error response. */ ResponseInfo<ErrorResponseBody> processUnhandledError(HttpProcessingState state, Object msg, Throwable cause) { RequestInfo<?> requestInfo = getRequestInfo(state, msg); // Run the error through the riposteUnhandledErrorHandler ErrorResponseInfo contentFromErrorHandler = riposteUnhandledErrorHandler.handleError(cause, requestInfo); ResponseInfo<ErrorResponseBody> responseInfo = new FullResponseInfo<>(); setupResponseInfoBasedOnErrorResponseInfo(responseInfo, contentFromErrorHandler); return responseInfo; }
/** * Produces a generic error response. Call this if you know the error is a non-normal unhandled error of the "how * did we get here, this should never happen" variety, or if other attempts to deal with the error failed and you * need a guaranteed fallback that will produce a generic error response that follows our error contract. If you * have an error that happened during normal processing you should try {@link #processError(HttpProcessingState, * Object, Throwable)} instead in order to get an error response that is better tailored to the given error rather * than this one which guarantees a somewhat unhelpful generic error response. */ ResponseInfo<ErrorResponseBody> processUnhandledError(HttpProcessingState state, Object msg, Throwable cause) { RequestInfo<?> requestInfo = getRequestInfo(state, msg); // Run the error through the riposteUnhandledErrorHandler ErrorResponseInfo contentFromErrorHandler = riposteUnhandledErrorHandler.handleError(cause, requestInfo); ResponseInfo<ErrorResponseBody> responseInfo = new FullResponseInfo<>(); setupResponseInfoBasedOnErrorResponseInfo(responseInfo, contentFromErrorHandler); return responseInfo; }
/** * Attempts to process the given error using the "normal" error handler {@link #riposteErrorHandler} to produce the * most specific error response possible for the given error. If that fails for any reason then the unhandled error * handler will take over to guarantee the user gets a generic error response that still follows our error contract. * If you already know your error is a non-normal unhandled error of the "how did we get here, this should never * happen" variety you can (and should) directly call {@link #processUnhandledError(HttpProcessingState, Object, * Throwable)} instead. */ protected ResponseInfo<ErrorResponseBody> processError(HttpProcessingState state, Object msg, Throwable cause) { RequestInfo<?> requestInfo = getRequestInfo(state, msg); try { ErrorResponseInfo contentFromErrorHandler = riposteErrorHandler.maybeHandleError(cause, requestInfo); if (contentFromErrorHandler != null) { // The regular error handler did handle the error. Setup our ResponseInfo. ResponseInfo<ErrorResponseBody> responseInfo = new FullResponseInfo<>(); setupResponseInfoBasedOnErrorResponseInfo(responseInfo, contentFromErrorHandler); return responseInfo; } } catch (Throwable errorHandlerFailed) { logger.error("An unexpected problem occurred while trying to handle an error.", errorHandlerFailed); } // If we reach here then it means the regular handler didn't handle the error (or blew up trying to handle it), // so the riposteUnhandledErrorHandler should take care of it. return processUnhandledError(state, msg, cause); }
/** * Attempts to process the given error using the "normal" error handler {@link #riposteErrorHandler} to produce the * most specific error response possible for the given error. If that fails for any reason then the unhandled error * handler will take over to guarantee the user gets a generic error response that still follows our error contract. * If you already know your error is a non-normal unhandled error of the "how did we get here, this should never * happen" variety you can (and should) directly call {@link #processUnhandledError(HttpProcessingState, Object, * Throwable)} instead. */ protected ResponseInfo<ErrorResponseBody> processError(HttpProcessingState state, Object msg, Throwable cause) { RequestInfo<?> requestInfo = getRequestInfo(state, msg); try { ErrorResponseInfo contentFromErrorHandler = riposteErrorHandler.maybeHandleError(cause, requestInfo); if (contentFromErrorHandler != null) { // The regular error handler did handle the error. Setup our ResponseInfo. ResponseInfo<ErrorResponseBody> responseInfo = new FullResponseInfo<>(); setupResponseInfoBasedOnErrorResponseInfo(responseInfo, contentFromErrorHandler); return responseInfo; } } catch (Throwable errorHandlerFailed) { logger.error("An unexpected problem occurred while trying to handle an error.", errorHandlerFailed); } // If we reach here then it means the regular handler didn't handle the error (or blew up trying to handle it), // so the riposteUnhandledErrorHandler should take care of it. return processUnhandledError(state, msg, cause); }
@Test public void processUnhandledError_uses_getRequestInfo_and_calls_riposteUnhandledErrorHandler_and_returns_value_of_setupResponseInfoBasedOnErrorResponseInfo() throws JsonProcessingException, UnexpectedMajorErrorHandlingError { // given HttpProcessingState stateMock = mock(HttpProcessingState.class); Object msg = new Object(); Throwable cause = new Exception(); ExceptionHandlingHandler handlerSpy = spy(handler); RequestInfo<?> requestInfoMock = mock(RequestInfo.class); ErrorResponseInfo errorResponseInfoMock = mock(ErrorResponseInfo.class); doReturn(requestInfoMock).when(handlerSpy).getRequestInfo(stateMock, msg); doReturn(errorResponseInfoMock).when(riposteUnhandledErrorHandlerMock).handleError(cause, requestInfoMock); // when ResponseInfo<ErrorResponseBody> response = handlerSpy.processUnhandledError(stateMock, msg, cause); // then verify(handlerSpy).getRequestInfo(stateMock, msg); verify(riposteUnhandledErrorHandlerMock).handleError(cause, requestInfoMock); ArgumentCaptor<ResponseInfo> responseInfoArgumentCaptor = ArgumentCaptor.forClass(ResponseInfo.class); verify(handlerSpy).setupResponseInfoBasedOnErrorResponseInfo(responseInfoArgumentCaptor.capture(), eq(errorResponseInfoMock)); ResponseInfo<ErrorResponseBody> responseInfoPassedIntoSetupMethod = responseInfoArgumentCaptor.getValue(); assertThat(response, is(responseInfoPassedIntoSetupMethod)); }
@Test public void setupResponseInfoBasedOnErrorResponseInfo_sets_response_content_and_httpStatusCode_and_adds_extra_headers() { // given ResponseInfo<ErrorResponseBody> responseInfo = new FullResponseInfo<>(); ErrorResponseBody errorResponseBodyMock = mock(ErrorResponseBody.class); int httpStatusCode = 42; Map<String, List<String>> extraHeaders = new HashMap<>(); extraHeaders.put("key1", Arrays.asList("foo", "bar")); extraHeaders.put("key2", Arrays.asList("baz")); ErrorResponseInfo errorInfoMock = mock(ErrorResponseInfo.class); doReturn(errorResponseBodyMock).when(errorInfoMock).getErrorResponseBody(); doReturn(httpStatusCode).when(errorInfoMock).getErrorHttpStatusCode(); doReturn(extraHeaders).when(errorInfoMock).getExtraHeadersToAddToResponse(); // when handler.setupResponseInfoBasedOnErrorResponseInfo(responseInfo, errorInfoMock); // then assertThat(responseInfo.getContentForFullResponse(), is(errorResponseBodyMock)); assertThat(responseInfo.getHttpStatusCode(), is(httpStatusCode)); int numIndividualValuesInHeaderMap = extraHeaders.entrySet().stream().map(entry -> entry.getValue()).mapToInt(list -> list.size()).sum(); assertThat(responseInfo.getHeaders().entries().size(), is(numIndividualValuesInHeaderMap)); extraHeaders.entrySet().stream().forEach(expectedEntry -> assertThat(responseInfo.getHeaders().getAll(expectedEntry.getKey()), is(expectedEntry.getValue()))); }
@Test public void processError_gets_requestInfo_then_calls_riposteErrorHandler_then_converts_to_response_using_setupResponseInfoBasedOnErrorResponseInfo() throws UnexpectedMajorErrorHandlingError, JsonProcessingException { // given HttpProcessingState stateMock = mock(HttpProcessingState.class); Object msg = new Object(); Throwable cause = new Exception(); ExceptionHandlingHandler handlerSpy = spy(handler); RequestInfo<?> requestInfoMock = mock(RequestInfo.class); ErrorResponseInfo errorResponseInfoMock = mock(ErrorResponseInfo.class); RiposteErrorHandler riposteErrorHandlerMock = mock(RiposteErrorHandler.class); Whitebox.setInternalState(handlerSpy, "riposteErrorHandler", riposteErrorHandlerMock); doReturn(requestInfoMock).when(handlerSpy).getRequestInfo(stateMock, msg); doReturn(errorResponseInfoMock).when(riposteErrorHandlerMock).maybeHandleError(cause, requestInfoMock); // when ResponseInfo<ErrorResponseBody> response = handlerSpy.processError(stateMock, msg, cause); // then verify(handlerSpy).getRequestInfo(stateMock, msg); verify(riposteErrorHandlerMock).maybeHandleError(cause, requestInfoMock); ArgumentCaptor<ResponseInfo> responseInfoArgumentCaptor = ArgumentCaptor.forClass(ResponseInfo.class); verify(handlerSpy).setupResponseInfoBasedOnErrorResponseInfo(responseInfoArgumentCaptor.capture(), eq(errorResponseInfoMock)); ResponseInfo<ErrorResponseBody> responseInfoPassedIntoSetupMethod = responseInfoArgumentCaptor.getValue(); assertThat(response, is(responseInfoPassedIntoSetupMethod)); }
@Test public void setupResponseInfoBasedOnErrorResponseInfo_sets_response_content_and_httpStatusCode_and_ignores_extra_headers_if_extra_headers_is_null() { // given ResponseInfo<ErrorResponseBody> responseInfo = new FullResponseInfo<>(); ErrorResponseBody errorResponseBodyMock = mock(ErrorResponseBody.class); int httpStatusCode = 42; ErrorResponseInfo errorInfoMock = mock(ErrorResponseInfo.class); doReturn(errorResponseBodyMock).when(errorInfoMock).getErrorResponseBody(); doReturn(httpStatusCode).when(errorInfoMock).getErrorHttpStatusCode(); doReturn(null).when(errorInfoMock).getExtraHeadersToAddToResponse(); // when handler.setupResponseInfoBasedOnErrorResponseInfo(responseInfo, errorInfoMock); // then assertThat(responseInfo.getContentForFullResponse(), is(errorResponseBodyMock)); assertThat(responseInfo.getHttpStatusCode(), is(httpStatusCode)); assertThat(responseInfo.getHeaders().entries().size(), is(0)); }