Skip to content

Commit d5c2b04

Browse files
authored
fix: change the "500" status code to "400" when the request has invalid UUID (#229)
Closes: MRSPECS-52
1 parent 4b27899 commit d5c2b04

6 files changed

Lines changed: 70 additions & 9 deletions

File tree

NEWS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
* Description ([ISSUE](https://folio-org.atlassian.net/browse/ISSUE))
1111

1212
### Bug fixes
13-
* Description ([ISSUE](https://folio-org.atlassian.net/browse/ISSUE))
13+
* Change the "500" status code to "400" when the request has invalid UUID ([MRSPECS-52](https://folio-org.atlassian.net/browse/MRSPECS-52))
1414

1515
### Tech Dept
1616
* Add integration test to cover sync endpoint for authority ([MRSPECS-105](https://folio-org.atlassian.net/browse/MRSPECS-105))

mod-record-specifications-server/src/main/java/org/folio/rspec/controller/handler/MethodArgumentTypeMismatchExceptionHandler.java

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package org.folio.rspec.controller.handler;
22

33
import static org.folio.rspec.domain.dto.ErrorCode.INVALID_QUERY_ENUM_VALUE;
4+
import static org.folio.rspec.domain.dto.ErrorCode.INVALID_QUERY_UUID_VALUE;
45

56
import java.util.Arrays;
7+
import java.util.UUID;
68
import java.util.regex.Pattern;
79
import lombok.RequiredArgsConstructor;
810
import org.folio.rspec.domain.dto.Error;
@@ -30,32 +32,50 @@ public class MethodArgumentTypeMismatchExceptionHandler implements ServiceExcept
3032
public ResponseEntity<ErrorCollection> handleException(Exception e) {
3133
var exception = (MethodArgumentTypeMismatchException) e;
3234
var requiredType = exception.getRequiredType();
33-
var errorCollection = new ErrorCollection();
3435

3536
if (requiredType != null && requiredType.isEnum()) {
36-
errorCollection.addErrorsItem(buildEnumError(exception, requiredType));
37-
return ResponseEntity.status(HttpStatus.UNPROCESSABLE_CONTENT).body(errorCollection);
38-
} else {
39-
errorCollection.addErrorsItem(buildUnexpectedError(exception));
40-
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorCollection);
37+
return buildResponse(HttpStatus.UNPROCESSABLE_CONTENT, buildEnumError(exception, requiredType));
4138
}
39+
if (requiredType != null && requiredType.isAssignableFrom(UUID.class)) {
40+
return buildResponse(HttpStatus.BAD_REQUEST, buildUuidError(exception));
41+
}
42+
return buildResponse(HttpStatus.INTERNAL_SERVER_ERROR, buildUnexpectedError(exception));
4243
}
4344

4445
@Override
4546
public boolean canHandle(Exception e) {
4647
return e instanceof MethodArgumentTypeMismatchException;
4748
}
4849

50+
private ResponseEntity<ErrorCollection> buildResponse(HttpStatus status, Error error) {
51+
return ResponseEntity.status(status).body(new ErrorCollection().addErrorsItem(error));
52+
}
53+
4954
private Error buildEnumError(MethodArgumentTypeMismatchException e, Class<?> requiredType) {
50-
var message = buildErrorMessage(e, requiredType.getEnumConstants());
55+
var message = buildEnumErrorMessage(e, requiredType.getEnumConstants());
5156
return new Error()
5257
.message(message)
5358
.code(INVALID_QUERY_ENUM_VALUE.getCode())
5459
.type(INVALID_QUERY_ENUM_VALUE.getType())
5560
.addParametersItem(new Parameter().key(e.getName()).value(String.valueOf(e.getValue())));
5661
}
5762

58-
private String buildErrorMessage(MethodArgumentTypeMismatchException e, Object[] enumConstants) {
63+
private Error buildUuidError(MethodArgumentTypeMismatchException e) {
64+
var inputValue = String.valueOf(e.getValue());
65+
var message = buildUuidErrorMessage(inputValue);
66+
return new Error()
67+
.message(message)
68+
.code(INVALID_QUERY_UUID_VALUE.getCode())
69+
.type(INVALID_QUERY_UUID_VALUE.getType())
70+
.addParametersItem(new Parameter().key(e.getName()).value(inputValue));
71+
}
72+
73+
private String buildUuidErrorMessage(String inputValue) {
74+
return translationService.format(INVALID_QUERY_UUID_VALUE.getMessageKey(),
75+
INVALID_VALUE_MSG_ARG, inputValue);
76+
}
77+
78+
private String buildEnumErrorMessage(MethodArgumentTypeMismatchException e, Object[] enumConstants) {
5979
return translationService.format(INVALID_QUERY_ENUM_VALUE.getMessageKey(),
6080
INVALID_VALUE_MSG_ARG, getInvalidValue(e.getRootCause()),
6181
POSSIBLE_VALUES_MSG_ARG, translationService.formatList(Arrays.asList(enumConstants)));

mod-record-specifications-server/src/main/java/org/folio/rspec/domain/dto/ErrorCode.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ public enum ErrorCode {
77

88
INVALID_QUERY_VALUE("invalid-query-value", "101", null),
99
INVALID_QUERY_ENUM_VALUE("invalid-query-enum-value", "102", "invalid.request.query-param.enum"),
10+
INVALID_QUERY_UUID_VALUE("invalid-uuid-value", "111", "invalid.request.query-param.uuid"),
1011
INVALID_REQUEST_PARAMETER("invalid-request-parameter", "103", null),
1112
DUPLICATE_FIELD_TAG("duplicate-specification-field-tag", "104", "specification.field.tag.duplicate"),
1213
SPECIFICATION_FETCH_FAILED("specification-fetch-failed", "105", "specification.fetch.failed"),

mod-record-specifications-server/src/test/java/org/folio/rspec/controller/SpecificationStorageControllerTest.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.folio.rspec.controller;
22

3+
import static org.folio.support.ApiEndpoints.SPECIFICATION_PATH;
34
import static org.folio.support.ApiEndpoints.specificationFieldsPath;
45
import static org.folio.support.ApiEndpoints.specificationPath;
56
import static org.folio.support.ApiEndpoints.specificationRulePath;
@@ -164,6 +165,20 @@ void getSpecification_returnSpecification(@Random UUID specificationId,
164165
verify(specificationService).getSpecificationById(specificationId, IncludeParam.NONE);
165166
}
166167

168+
@Test
169+
void getSpecification_negative_invalidUuid() throws Exception {
170+
var specificationId = "invalid-uuid";
171+
172+
var requestBuilder = get(SPECIFICATION_PATH.formatted(specificationId))
173+
.contentType(APPLICATION_JSON);
174+
175+
mockMvc.perform(requestBuilder)
176+
.andExpect(status().isBadRequest())
177+
.andExpect(jsonPath("$.errors.size()", is(1)))
178+
.andExpect(jsonPath("$.errors.[*].message", hasItem(is("Invalid value [%s]. Must be a valid UUID."
179+
.formatted(specificationId)))));
180+
}
181+
167182
@Test
168183
void getSpecificationRules_returnSpecificationRules(@Random SpecificationRuleDto ruleDto) throws Exception {
169184
var specificationId = UUID.randomUUID();

mod-record-specifications-server/src/test/java/org/folio/rspec/controller/handler/MethodArgumentTypeMismatchExceptionHandlerTest.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import static org.mockito.ArgumentMatchers.eq;
1010
import static org.mockito.Mockito.when;
1111

12+
import java.util.UUID;
1213
import org.folio.rspec.domain.dto.Error;
1314
import org.folio.rspec.service.i18n.ExtendedTranslationService;
1415
import org.folio.spring.testing.type.UnitTest;
@@ -63,6 +64,29 @@ void testHandleException_unexpectedEnumValue() {
6364
.containsExactly(expectedMessage, "invalid-query-enum-value", "102", "arg1", "invalidValue");
6465
}
6566

67+
@Test
68+
void testHandleException_invalidUuidValue() {
69+
var expectedMessage = "error message";
70+
var handlerClass = UUID.class;
71+
var invalidValue = "not-a-uuid";
72+
73+
when(exception.getName()).thenReturn("arg1");
74+
when(exception.getRequiredType()).thenAnswer(invocation -> handlerClass);
75+
when(exception.getValue()).thenReturn(invalidValue);
76+
when(translationService.format(anyString(), eq("invalidValue"), eq(invalidValue)))
77+
.thenReturn(expectedMessage);
78+
79+
var response = handler.handleException(exception);
80+
81+
assertThat(response.getStatusCode().value()).isEqualTo(HttpStatus.BAD_REQUEST.value());
82+
assertThat(response.getBody()).isNotNull();
83+
assertThat(response.getBody().getErrors()).isNotNull().hasSize(1);
84+
assertThat(response.getBody().getErrors().getFirst())
85+
.extracting(Error::getMessage, Error::getType, Error::getCode, error -> error.getParameters().getFirst().getKey(),
86+
error -> error.getParameters().getFirst().getValue())
87+
.containsExactly(expectedMessage, "invalid-uuid-value", "111", "arg1", invalidValue);
88+
}
89+
6690
@Test
6791
void testHandleException_unexpectedError() {
6892
var handlerClass = MethodArgumentTypeMismatchExceptionHandler.class;

translations/mod-record-specifications/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"control-field.resource.not-allowed": "Cannot define {parameter}s for 00X control fields.",
1111

1212
"invalid.request.query-param.enum": "Unexpected value [{invalidValue}]. Possible values: [{possibleValues}].",
13+
"invalid.request.query-param.uuid": "Invalid value [{invalidValue}]. Must be a valid UUID.",
1314
"Pattern.SpecificationFieldChangeDto.tag": "A ''{parameter}'' field must contain three characters and can only accept numbers 0-9.",
1415
"Pattern.IndicatorCodeChangeDto.code": "A ''{parameter}'' field must contain one character and can only accept numbers 0-9, letters a-z or a '#'.",
1516
"Pattern.SubfieldChangeDto.code": "A ''{parameter}'' field must contain one character and can only accept numbers 0-9 or letters a-z.",

0 commit comments

Comments
 (0)