diff --git a/belgif-rest-problem-it/belgif-rest-problem-it-common/src/main/java/io/github/belgif/rest/problem/it/AbstractJacksonSerializationTest.java b/belgif-rest-problem-it/belgif-rest-problem-it-common/src/main/java/io/github/belgif/rest/problem/it/AbstractJacksonSerializationTest.java index f6cb5c51..f2dc1015 100644 --- a/belgif-rest-problem-it/belgif-rest-problem-it-common/src/main/java/io/github/belgif/rest/problem/it/AbstractJacksonSerializationTest.java +++ b/belgif-rest-problem-it/belgif-rest-problem-it-common/src/main/java/io/github/belgif/rest/problem/it/AbstractJacksonSerializationTest.java @@ -80,7 +80,7 @@ void retryAfterProblem() throws IOException { @Test void badRequestProblemReplacedSsin() throws IOException { BadRequestProblem problem = new BadRequestProblem( - InputValidationIssues.replacedSsin(InEnum.BODY, "parent[1].ssin", "12345678901", "23456789012")); + InputValidationIssues.replacedSsin(InEnum.BODY, "/parent/1/ssin", "12345678901", "23456789012")); assertSerializationRoundtrip(problem); } @@ -182,7 +182,7 @@ void legacyInvalidParamProblem() throws IOException { + " \"detail\": \"The input message is incorrect\",\n" + " \"invalidParams\": [ {\n" + " \"in\": \"body\",\n" - + " \"name\": \"sector\",\n" + + " \"name\": \"/sector\",\n" + " \"reason\": \"must be less than or equal to 999\",\n" + " \"value\": 9999,\n" + " \"issueType\": \"schemaViolation\"\n" @@ -239,7 +239,7 @@ void issueWithStatusAndInstance() throws IOException { @Test void issueWithNullValue() throws IOException { BadRequestProblem problem = new BadRequestProblem( - new InputValidationIssue(InEnum.BODY, "id", null)); + new InputValidationIssue(InEnum.BODY, "/id", null)); String json = writeProblem(problem); assertThat(json).doesNotContain("null"); assertSerializationRoundtrip(problem); @@ -249,7 +249,7 @@ void issueWithNullValue() throws IOException { void issueWithNullInputValue() throws IOException { ProblemConfig.setExtInputsArrayEnabled(true); BadRequestProblem problem = new BadRequestProblem(new InputValidationIssue() - .inputs(Input.body("a", null), Input.body("b", null))); + .inputs(Input.body("/a", null), Input.body("/b", null))); String json = writeProblem(problem); assertThat(json).doesNotContain("null"); assertSerializationRoundtrip(problem); diff --git a/belgif-rest-problem-it/belgif-rest-problem-it-common/src/main/java/io/github/belgif/rest/problem/it/AbstractRestProblemIT.java b/belgif-rest-problem-it/belgif-rest-problem-it-common/src/main/java/io/github/belgif/rest/problem/it/AbstractRestProblemIT.java index f30aae1f..ff2d22a3 100644 --- a/belgif-rest-problem-it/belgif-rest-problem-it-common/src/main/java/io/github/belgif/rest/problem/it/AbstractRestProblemIT.java +++ b/belgif-rest-problem-it/belgif-rest-problem-it-common/src/main/java/io/github/belgif/rest/problem/it/AbstractRestProblemIT.java @@ -332,11 +332,11 @@ public void constraintViolationBody() { .statusCode(400) .body("type", equalTo("urn:problem-type:belgif:badRequest")) .body("issues[0].in", equalTo("body")) - .body("issues[0].name", equalTo("email")) + .body("issues[0].name", equalTo("/email")) .body("issues[0].value", equalTo("mymail.com")) .body("issues[0].detail", equalTo("must be a well-formed email address")) .body("issues[1].in", equalTo("body")) - .body("issues[1].name", equalTo("name")) + .body("issues[1].name", equalTo("/name")) .body("issues[1].value", nullValue()) .body("issues[1].detail", equalTo("must not be blank")); } @@ -352,11 +352,11 @@ public void constraintViolationBodyNested() { .statusCode(400) .body("type", equalTo("urn:problem-type:belgif:badRequest")) .body("issues[0].in", equalTo("body")) - .body("issues[0].name", equalTo("nested.email")) + .body("issues[0].name", equalTo("/nested/email")) .body("issues[0].value", equalTo("mymail.com")) .body("issues[0].detail", equalTo("must be a well-formed email address")) .body("issues[1].in", equalTo("body")) - .body("issues[1].name", equalTo("nested.name")) + .body("issues[1].name", equalTo("/nested/name")) .body("issues[1].value", nullValue()) .body("issues[1].detail", equalTo("must not be blank")); } @@ -372,11 +372,11 @@ public void constraintViolationBodyInheritance() { .statusCode(400) .body("type", equalTo("urn:problem-type:belgif:badRequest")) .body("issues[0].in", equalTo("body")) - .body("issues[0].name", equalTo("email")) + .body("issues[0].name", equalTo("/email")) .body("issues[0].value", equalTo("mymail.com")) .body("issues[0].detail", equalTo("must be a well-formed email address")) .body("issues[1].in", equalTo("body")) - .body("issues[1].name", equalTo("name")) + .body("issues[1].name", equalTo("/name")) .body("issues[1].value", nullValue()) .body("issues[1].detail", equalTo("must not be blank")); } @@ -389,7 +389,7 @@ public void jacksonMismatchedInputException() { .statusCode(400) .body("type", equalTo("urn:problem-type:belgif:badRequest")) .body("issues[0].in", equalTo("body")) - .body("issues[0].name", equalTo("id")) + .body("issues[0].name", equalTo("/id")) .body("issues[0].detail", equalTo("must not be null")); } @@ -542,7 +542,7 @@ public void invalidJsonNested() { .body("issues[0].title", equalTo("Input value is invalid with respect to the schema")) .body("issues[0].detail", equalTo("JSON syntax error")) .body("issues[0].in", equalTo("body")) - .body("issues[0].name", equalTo("nested")); + .body("issues[0].name", equalTo("/nested")); } @Test @@ -558,7 +558,7 @@ public void invalidJsonType() { .body("issues[0].title", equalTo("Input value is invalid with respect to the schema")) .body("issues[0].detail", equalTo("not a valid `int` value")) .body("issues[0].in", equalTo("body")) - .body("issues[0].name", equalTo("age")) + .body("issues[0].name", equalTo("/age")) .body("issues[0].value", equalTo("twenty-two")); } diff --git a/belgif-rest-problem-it/belgif-rest-problem-jackson2-latest-it/pom.xml b/belgif-rest-problem-it/belgif-rest-problem-jackson2-latest-it/pom.xml index edafaffe..978265c4 100644 --- a/belgif-rest-problem-it/belgif-rest-problem-jackson2-latest-it/pom.xml +++ b/belgif-rest-problem-it/belgif-rest-problem-jackson2-latest-it/pom.xml @@ -25,6 +25,12 @@ 2.17.0 provided + + org.slf4j + slf4j-api + 2.0.18 + provided + org.junit.jupiter junit-jupiter diff --git a/belgif-rest-problem-it/belgif-rest-problem-jackson2-minimal-it/pom.xml b/belgif-rest-problem-it/belgif-rest-problem-jackson2-minimal-it/pom.xml index dd9b195d..d7c564fd 100644 --- a/belgif-rest-problem-it/belgif-rest-problem-jackson2-minimal-it/pom.xml +++ b/belgif-rest-problem-it/belgif-rest-problem-jackson2-minimal-it/pom.xml @@ -25,6 +25,12 @@ ${version.jackson.minimal} provided + + org.slf4j + slf4j-api + 2.0.18 + provided + org.junit.jupiter junit-jupiter diff --git a/belgif-rest-problem-it/belgif-rest-problem-jackson3-it/pom.xml b/belgif-rest-problem-it/belgif-rest-problem-jackson3-it/pom.xml index 63f4e637..6b657c2f 100644 --- a/belgif-rest-problem-it/belgif-rest-problem-jackson3-it/pom.xml +++ b/belgif-rest-problem-it/belgif-rest-problem-jackson3-it/pom.xml @@ -25,6 +25,12 @@ ${version.jackson3.minimal} provided + + org.slf4j + slf4j-api + 2.0.18 + provided + org.junit.jupiter junit-jupiter diff --git a/belgif-rest-problem-it/belgif-rest-problem-jakarta-ee-it/src/main/java/io/github/belgif/rest/problem/LocalDateConverter.java b/belgif-rest-problem-it/belgif-rest-problem-jakarta-ee-it/src/main/java/io/github/belgif/rest/problem/LocalDateConverter.java index ca9a72da..d89f0dc4 100644 --- a/belgif-rest-problem-it/belgif-rest-problem-jakarta-ee-it/src/main/java/io/github/belgif/rest/problem/LocalDateConverter.java +++ b/belgif-rest-problem-it/belgif-rest-problem-jakarta-ee-it/src/main/java/io/github/belgif/rest/problem/LocalDateConverter.java @@ -10,6 +10,7 @@ import jakarta.ws.rs.ext.Provider; import io.github.belgif.rest.problem.api.InEnum; +import io.github.belgif.rest.problem.api.InputValidationIssue; import io.github.belgif.rest.problem.ee.server.jaxrs.AbstractInputParamConverterProvider; @Provider @@ -31,7 +32,9 @@ protected LocalDate fromString(InEnum in, String name, String value) { try { return LocalDate.parse(value, LOCAL_DATE_FORMATTER); } catch (DateTimeParseException e) { - throw new BadRequestProblem(schemaViolation(in, name, value, "date has invalid format")); + throw new BadRequestProblem( + schemaViolation(in, InputValidationIssue.transformName(in, name), value, + "date has invalid format")); } } diff --git a/belgif-rest-problem-it/belgif-rest-problem-java-ee-it/src/main/java/io/github/belgif/rest/problem/LocalDateConverter.java b/belgif-rest-problem-it/belgif-rest-problem-java-ee-it/src/main/java/io/github/belgif/rest/problem/LocalDateConverter.java index dafb618e..b0bb9ef7 100644 --- a/belgif-rest-problem-it/belgif-rest-problem-java-ee-it/src/main/java/io/github/belgif/rest/problem/LocalDateConverter.java +++ b/belgif-rest-problem-it/belgif-rest-problem-java-ee-it/src/main/java/io/github/belgif/rest/problem/LocalDateConverter.java @@ -10,6 +10,7 @@ import javax.ws.rs.ext.Provider; import io.github.belgif.rest.problem.api.InEnum; +import io.github.belgif.rest.problem.api.InputValidationIssue; import io.github.belgif.rest.problem.ee.server.jaxrs.AbstractInputParamConverterProvider; @Provider @@ -31,7 +32,9 @@ protected LocalDate fromString(InEnum in, String name, String value) { try { return LocalDate.parse(value, LOCAL_DATE_FORMATTER); } catch (DateTimeParseException e) { - throw new BadRequestProblem(schemaViolation(in, name, value, "date has invalid format")); + throw new BadRequestProblem( + schemaViolation(in, InputValidationIssue.transformName(in, name), value, + "date has invalid format")); } } diff --git a/belgif-rest-problem-it/belgif-rest-problem-quarkus-it/src/main/java/io/github/belgif/rest/problem/quarkus/it/LocalDateConverter.java b/belgif-rest-problem-it/belgif-rest-problem-quarkus-it/src/main/java/io/github/belgif/rest/problem/quarkus/it/LocalDateConverter.java index 3b56b2fe..1db1d9ba 100644 --- a/belgif-rest-problem-it/belgif-rest-problem-quarkus-it/src/main/java/io/github/belgif/rest/problem/quarkus/it/LocalDateConverter.java +++ b/belgif-rest-problem-it/belgif-rest-problem-quarkus-it/src/main/java/io/github/belgif/rest/problem/quarkus/it/LocalDateConverter.java @@ -11,6 +11,7 @@ import io.github.belgif.rest.problem.BadRequestProblem; import io.github.belgif.rest.problem.api.InEnum; +import io.github.belgif.rest.problem.api.InputValidationIssue; import io.github.belgif.rest.problem.ee.server.jaxrs.AbstractInputParamConverterProvider; @Provider @@ -32,7 +33,9 @@ protected LocalDate fromString(InEnum in, String name, String value) { try { return LocalDate.parse(value, LOCAL_DATE_FORMATTER); } catch (DateTimeParseException e) { - throw new BadRequestProblem(schemaViolation(in, name, value, "date has invalid format")); + throw new BadRequestProblem( + schemaViolation(in, InputValidationIssue.transformName(in, name), value, + "date has invalid format")); } } diff --git a/belgif-rest-problem-java-ee-core/src/main/java/io/github/belgif/rest/problem/ee/core/jaxrs/ProblemConfigurator.java b/belgif-rest-problem-java-ee-core/src/main/java/io/github/belgif/rest/problem/ee/core/jaxrs/ProblemConfigurator.java index 9f87b6cf..4a484488 100644 --- a/belgif-rest-problem-java-ee-core/src/main/java/io/github/belgif/rest/problem/ee/core/jaxrs/ProblemConfigurator.java +++ b/belgif-rest-problem-java-ee-core/src/main/java/io/github/belgif/rest/problem/ee/core/jaxrs/ProblemConfigurator.java @@ -25,6 +25,7 @@ public void contextInitialized(ServletContextEvent sce) { setBooleanConfig(sce, ProblemConfig.PROPERTY_STACK_TRACE_ENABLED, ProblemConfig::setStackTraceEnabled); setBooleanConfig(sce, ProblemConfig.PROPERTY_EXT_ISSUE_TYPES_ENABLED, ProblemConfig::setExtIssueTypesEnabled); setBooleanConfig(sce, ProblemConfig.PROPERTY_EXT_INPUTS_ARRAY_ENABLED, ProblemConfig::setExtInputsArrayEnabled); + setBooleanConfig(sce, ProblemConfig.PROPERTY_JSON_POINTER_ENABLED, ProblemConfig::setJsonPointerEnabled); } private void setBooleanConfig(ServletContextEvent sce, String key, Consumer configSetter) { diff --git a/belgif-rest-problem-java-ee-core/src/test/java/io/github/belgif/rest/problem/ee/core/jaxrs/ProblemConfiguratorTest.java b/belgif-rest-problem-java-ee-core/src/test/java/io/github/belgif/rest/problem/ee/core/jaxrs/ProblemConfiguratorTest.java index 808830b5..a0dcbef3 100644 --- a/belgif-rest-problem-java-ee-core/src/test/java/io/github/belgif/rest/problem/ee/core/jaxrs/ProblemConfiguratorTest.java +++ b/belgif-rest-problem-java-ee-core/src/test/java/io/github/belgif/rest/problem/ee/core/jaxrs/ProblemConfiguratorTest.java @@ -37,15 +37,18 @@ void notConfigured() { boolean stackTraceEnabledBefore = ProblemConfig.isStackTraceEnabled(); boolean extIssueTypesEnabledBefore = ProblemConfig.isExtIssueTypesEnabled(); boolean extInputsArrayEnabledBefore = ProblemConfig.isExtInputsArrayEnabled(); + boolean jsonPointerEnabledBefore = ProblemConfig.isJsonPointerEnabled(); when(servletContext.getInitParameter(ProblemConfig.PROPERTY_I18N_ENABLED)).thenReturn(null); when(servletContext.getInitParameter(ProblemConfig.PROPERTY_STACK_TRACE_ENABLED)).thenReturn(null); when(servletContext.getInitParameter(ProblemConfig.PROPERTY_EXT_ISSUE_TYPES_ENABLED)).thenReturn(null); when(servletContext.getInitParameter(ProblemConfig.PROPERTY_EXT_INPUTS_ARRAY_ENABLED)).thenReturn(null); + when(servletContext.getInitParameter(ProblemConfig.PROPERTY_JSON_POINTER_ENABLED)).thenReturn(null); configurator.contextInitialized(new ServletContextEvent(servletContext)); assertThat(ProblemConfig.isI18nEnabled()).isEqualTo(i18nEnabledBefore); assertThat(ProblemConfig.isStackTraceEnabled()).isEqualTo(stackTraceEnabledBefore); assertThat(ProblemConfig.isExtIssueTypesEnabled()).isEqualTo(extIssueTypesEnabledBefore); assertThat(ProblemConfig.isExtInputsArrayEnabled()).isEqualTo(extInputsArrayEnabledBefore); + assertThat(ProblemConfig.isJsonPointerEnabled()).isEqualTo(jsonPointerEnabledBefore); } @Test @@ -54,11 +57,13 @@ void enabledViaInitParam() { when(servletContext.getInitParameter(ProblemConfig.PROPERTY_STACK_TRACE_ENABLED)).thenReturn("true"); when(servletContext.getInitParameter(ProblemConfig.PROPERTY_EXT_ISSUE_TYPES_ENABLED)).thenReturn("true"); when(servletContext.getInitParameter(ProblemConfig.PROPERTY_EXT_INPUTS_ARRAY_ENABLED)).thenReturn("true"); + when(servletContext.getInitParameter(ProblemConfig.PROPERTY_JSON_POINTER_ENABLED)).thenReturn("true"); configurator.contextInitialized(new ServletContextEvent(servletContext)); assertThat(ProblemConfig.isI18nEnabled()).isTrue(); assertThat(ProblemConfig.isStackTraceEnabled()).isTrue(); assertThat(ProblemConfig.isExtIssueTypesEnabled()).isTrue(); assertThat(ProblemConfig.isExtInputsArrayEnabled()).isTrue(); + assertThat(ProblemConfig.isJsonPointerEnabled()).isTrue(); } @Test @@ -67,11 +72,13 @@ void disabledViaInitParam() { when(servletContext.getInitParameter(ProblemConfig.PROPERTY_STACK_TRACE_ENABLED)).thenReturn("false"); when(servletContext.getInitParameter(ProblemConfig.PROPERTY_EXT_ISSUE_TYPES_ENABLED)).thenReturn("false"); when(servletContext.getInitParameter(ProblemConfig.PROPERTY_EXT_INPUTS_ARRAY_ENABLED)).thenReturn("false"); + when(servletContext.getInitParameter(ProblemConfig.PROPERTY_JSON_POINTER_ENABLED)).thenReturn("false"); configurator.contextInitialized(new ServletContextEvent(servletContext)); assertThat(ProblemConfig.isI18nEnabled()).isFalse(); assertThat(ProblemConfig.isStackTraceEnabled()).isFalse(); assertThat(ProblemConfig.isExtIssueTypesEnabled()).isFalse(); assertThat(ProblemConfig.isExtInputsArrayEnabled()).isFalse(); + assertThat(ProblemConfig.isJsonPointerEnabled()).isFalse(); } @Test @@ -79,12 +86,14 @@ void disabledViaInitParam() { @SetSystemProperty(key = ProblemConfig.PROPERTY_STACK_TRACE_ENABLED, value = "true") @SetSystemProperty(key = ProblemConfig.PROPERTY_EXT_ISSUE_TYPES_ENABLED, value = "true") @SetSystemProperty(key = ProblemConfig.PROPERTY_EXT_INPUTS_ARRAY_ENABLED, value = "true") + @SetSystemProperty(key = ProblemConfig.PROPERTY_JSON_POINTER_ENABLED, value = "true") void enabledViaSystemProperties() { configurator.contextInitialized(new ServletContextEvent(servletContext)); assertThat(ProblemConfig.isI18nEnabled()).isTrue(); assertThat(ProblemConfig.isStackTraceEnabled()).isTrue(); assertThat(ProblemConfig.isExtIssueTypesEnabled()).isTrue(); assertThat(ProblemConfig.isExtInputsArrayEnabled()).isTrue(); + assertThat(ProblemConfig.isJsonPointerEnabled()).isTrue(); verifyNoInteractions(servletContext); } @@ -93,12 +102,14 @@ void enabledViaSystemProperties() { @SetSystemProperty(key = ProblemConfig.PROPERTY_STACK_TRACE_ENABLED, value = "false") @SetSystemProperty(key = ProblemConfig.PROPERTY_EXT_ISSUE_TYPES_ENABLED, value = "false") @SetSystemProperty(key = ProblemConfig.PROPERTY_EXT_INPUTS_ARRAY_ENABLED, value = "false") + @SetSystemProperty(key = ProblemConfig.PROPERTY_JSON_POINTER_ENABLED, value = "false") void disabledViaSystemProperty() { configurator.contextInitialized(new ServletContextEvent(servletContext)); assertThat(ProblemConfig.isI18nEnabled()).isFalse(); assertThat(ProblemConfig.isStackTraceEnabled()).isFalse(); assertThat(ProblemConfig.isExtIssueTypesEnabled()).isFalse(); assertThat(ProblemConfig.isExtInputsArrayEnabled()).isFalse(); + assertThat(ProblemConfig.isJsonPointerEnabled()).isFalse(); verifyNoInteractions(servletContext); } @@ -107,12 +118,14 @@ void disabledViaSystemProperty() { @SetEnvironmentVariable(key = ProblemConfig.PROPERTY_STACK_TRACE_ENABLED, value = "true") @SetEnvironmentVariable(key = ProblemConfig.PROPERTY_EXT_ISSUE_TYPES_ENABLED, value = "true") @SetEnvironmentVariable(key = ProblemConfig.PROPERTY_EXT_INPUTS_ARRAY_ENABLED, value = "true") + @SetEnvironmentVariable(key = ProblemConfig.PROPERTY_JSON_POINTER_ENABLED, value = "true") void enabledViaEnvironmentVariable() { configurator.contextInitialized(new ServletContextEvent(servletContext)); assertThat(ProblemConfig.isI18nEnabled()).isTrue(); assertThat(ProblemConfig.isStackTraceEnabled()).isTrue(); assertThat(ProblemConfig.isExtIssueTypesEnabled()).isTrue(); assertThat(ProblemConfig.isExtInputsArrayEnabled()).isTrue(); + assertThat(ProblemConfig.isJsonPointerEnabled()).isTrue(); verifyNoInteractions(servletContext); } @@ -121,12 +134,14 @@ void enabledViaEnvironmentVariable() { @SetEnvironmentVariable(key = ProblemConfig.PROPERTY_STACK_TRACE_ENABLED, value = "false") @SetEnvironmentVariable(key = ProblemConfig.PROPERTY_EXT_ISSUE_TYPES_ENABLED, value = "false") @SetEnvironmentVariable(key = ProblemConfig.PROPERTY_EXT_INPUTS_ARRAY_ENABLED, value = "false") + @SetEnvironmentVariable(key = ProblemConfig.PROPERTY_JSON_POINTER_ENABLED, value = "false") void disabledViaEnvironmentVariable() { configurator.contextInitialized(new ServletContextEvent(servletContext)); assertThat(ProblemConfig.isI18nEnabled()).isFalse(); assertThat(ProblemConfig.isStackTraceEnabled()).isFalse(); assertThat(ProblemConfig.isExtIssueTypesEnabled()).isFalse(); assertThat(ProblemConfig.isExtInputsArrayEnabled()).isFalse(); + assertThat(ProblemConfig.isJsonPointerEnabled()).isFalse(); verifyNoInteractions(servletContext); } @@ -135,16 +150,19 @@ void disabledViaEnvironmentVariable() { @SetEnvironmentVariable(key = ProblemConfig.PROPERTY_STACK_TRACE_ENABLED, value = "false") @SetEnvironmentVariable(key = ProblemConfig.PROPERTY_EXT_ISSUE_TYPES_ENABLED, value = "false") @SetEnvironmentVariable(key = ProblemConfig.PROPERTY_EXT_INPUTS_ARRAY_ENABLED, value = "false") + @SetEnvironmentVariable(key = ProblemConfig.PROPERTY_JSON_POINTER_ENABLED, value = "false") @SetSystemProperty(key = ProblemConfig.PROPERTY_I18N_ENABLED, value = "true") @SetSystemProperty(key = ProblemConfig.PROPERTY_STACK_TRACE_ENABLED, value = "true") @SetSystemProperty(key = ProblemConfig.PROPERTY_EXT_ISSUE_TYPES_ENABLED, value = "true") @SetSystemProperty(key = ProblemConfig.PROPERTY_EXT_INPUTS_ARRAY_ENABLED, value = "true") + @SetSystemProperty(key = ProblemConfig.PROPERTY_JSON_POINTER_ENABLED, value = "true") void systemPropertyHasPrecedenceOverEnvironmentVariable() { configurator.contextInitialized(new ServletContextEvent(servletContext)); assertThat(ProblemConfig.isI18nEnabled()).isTrue(); assertThat(ProblemConfig.isStackTraceEnabled()).isTrue(); assertThat(ProblemConfig.isExtIssueTypesEnabled()).isTrue(); assertThat(ProblemConfig.isExtInputsArrayEnabled()).isTrue(); + assertThat(ProblemConfig.isJsonPointerEnabled()).isTrue(); verifyNoInteractions(servletContext); } diff --git a/belgif-rest-problem-java-ee-server/src/main/java/io/github/belgif/rest/problem/ee/server/internal/ConstraintViolationUtil.java b/belgif-rest-problem-java-ee-server/src/main/java/io/github/belgif/rest/problem/ee/server/internal/ConstraintViolationUtil.java index 362eec01..9092bd2d 100644 --- a/belgif-rest-problem-java-ee-server/src/main/java/io/github/belgif/rest/problem/ee/server/internal/ConstraintViolationUtil.java +++ b/belgif-rest-problem-java-ee-server/src/main/java/io/github/belgif/rest/problem/ee/server/internal/ConstraintViolationUtil.java @@ -15,10 +15,13 @@ import javax.validation.Path.ParameterNode; import javax.ws.rs.BeanParam; +import com.fasterxml.jackson.core.JsonPointer; + import io.github.belgif.rest.problem.api.InEnum; import io.github.belgif.rest.problem.api.Input; import io.github.belgif.rest.problem.api.InputValidationIssue; import io.github.belgif.rest.problem.api.InputValidationIssues; +import io.github.belgif.rest.problem.config.ProblemConfig; import io.github.belgif.rest.problem.internal.AnnotationUtil; /** @@ -59,7 +62,10 @@ public static InputValidationIssue convertToInputValidationIssue(ConstraintViola private static Input determineInput(ConstraintViolation violation, MethodNode methodNode, List propertyPath, List propertyName) { - Input input = Input.body(String.join(".", propertyName), violation.getInvalidValue()); + + Input input = + Input.body(InputValidationIssue.getNameFromProperties(InEnum.BODY, propertyName), + violation.getInvalidValue()); Node last = propertyPath.get(propertyPath.size() - 1); Node parent = propertyPath.size() > 1 ? propertyPath.get(propertyPath.size() - 2) : null; if (last.getKind() == ElementKind.PARAMETER) { @@ -91,7 +97,8 @@ private static Input determineInput(ConstraintViolation violation, InEnum in = ParameterSourceMapper.map(annotation.annotationType()); if (in != null) { input.setIn(in); - input.setName((String) annotation.annotationType().getMethod("value").invoke(annotation)); + input.setName(InputValidationIssue.transformName(in, + (String) annotation.annotationType().getMethod("value").invoke(annotation))); } } } catch (NoSuchFieldException e) { @@ -101,6 +108,14 @@ private static Input determineInput(ConstraintViolation violation, } } } + + if (ProblemConfig.isJsonPointerEnabled() && input.getName() != null && input.getIn() != InEnum.BODY + && input.getName().charAt(0) == JsonPointer.SEPARATOR) { + // remove the '/' created with InputValidationIssue.getNameFromProperties used at the beginning of this + // method + input.setName(input.getName().substring(1)); + } + return input; } diff --git a/belgif-rest-problem-java-ee-server/src/test/java/io/github/belgif/rest/problem/ee/server/internal/ConstraintViolationUtilTest.java b/belgif-rest-problem-java-ee-server/src/test/java/io/github/belgif/rest/problem/ee/server/internal/ConstraintViolationUtilTest.java index 406a5368..29343b52 100644 --- a/belgif-rest-problem-java-ee-server/src/test/java/io/github/belgif/rest/problem/ee/server/internal/ConstraintViolationUtilTest.java +++ b/belgif-rest-problem-java-ee-server/src/test/java/io/github/belgif/rest/problem/ee/server/internal/ConstraintViolationUtilTest.java @@ -24,15 +24,22 @@ import javax.ws.rs.QueryParam; import javax.ws.rs.core.Response; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import io.github.belgif.rest.problem.api.InEnum; import io.github.belgif.rest.problem.api.InputValidationIssue; +import io.github.belgif.rest.problem.config.ProblemConfig; class ConstraintViolationUtilTest { private final Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); + @BeforeEach + void resetProblemConfig() { + ProblemConfig.reset(); + } + @Test void missingRequiredBody() throws Exception { Set> violations = @@ -60,6 +67,27 @@ void bodyProperty() throws Exception { assertThat(violations).hasSize(1); + InputValidationIssue issue = + ConstraintViolationUtil.convertToInputValidationIssue(violations.iterator().next()); + assertThat(issue.getIn()).isEqualTo(InEnum.BODY); + assertThat(issue.getName()).isEqualTo("/value"); + assertThat(issue.getValue()).isEqualTo(10); + assertThat(issue.getDetail()).isEqualTo("must be less than or equal to 5"); + } + + @Test + void bodyPropertyWithJsonPointerDisabled() throws Exception { + ProblemConfig.setJsonPointerEnabled(false); + + Body target = new Body(); + target.value = 10; + + Set> violations = + validator.forExecutables().validateParameters(new Resource(), + Resource.class.getMethod("bodyParam", Body.class), new Object[] { target }); + + assertThat(violations).hasSize(1); + InputValidationIssue issue = ConstraintViolationUtil.convertToInputValidationIssue(violations.iterator().next()); assertThat(issue.getIn()).isEqualTo(InEnum.BODY); @@ -80,6 +108,28 @@ void nestedBodyProperty() throws Exception { assertThat(violations).hasSize(1); + InputValidationIssue issue = + ConstraintViolationUtil.convertToInputValidationIssue(violations.iterator().next()); + assertThat(issue.getIn()).isEqualTo(InEnum.BODY); + assertThat(issue.getName()).isEqualTo("/nested/1/prop"); + assertThat(issue.getValue()).isNull(); + assertThat(issue.getDetail()).isEqualTo("must not be null"); + } + + @Test + void nestedBodyPropertyWithJsonPointerDisabled() throws Exception { + ProblemConfig.setJsonPointerEnabled(false); + + Body target = new Body(); + target.nested.add(new Nested("OK")); + target.nested.add(new Nested(null)); + + Set> violations = + validator.forExecutables().validateParameters(new Resource(), + Resource.class.getMethod("bodyParam", Body.class), new Object[] { target }); + + assertThat(violations).hasSize(1); + InputValidationIssue issue = ConstraintViolationUtil.convertToInputValidationIssue(violations.iterator().next()); assertThat(issue.getIn()).isEqualTo(InEnum.BODY); @@ -163,7 +213,7 @@ void formParam() throws Exception { InputValidationIssue issue = ConstraintViolationUtil.convertToInputValidationIssue(violations.iterator().next()); assertThat(issue.getIn()).isEqualTo(InEnum.BODY); - assertThat(issue.getName()).isEqualTo("form"); + assertThat(issue.getName()).isEqualTo("/form"); assertThat(issue.getValue()).isEqualTo(10); assertThat(issue.getDetail()).isEqualTo("must be less than or equal to 5"); } @@ -228,10 +278,10 @@ void beanParam() throws Exception { violations.stream().map(ConstraintViolationUtil::convertToInputValidationIssue) .sorted(Comparator.comparing(InputValidationIssue::getName)).collect(Collectors.toList()); - assertThat(issues.get(0).getIn()).isEqualTo(InEnum.HEADER); - assertThat(issues.get(0).getName()).isEqualTo("cookie"); - assertThat(issues.get(1).getIn()).isEqualTo(InEnum.BODY); - assertThat(issues.get(1).getName()).isEqualTo("form"); + assertThat(issues.get(0).getIn()).isEqualTo(InEnum.BODY); + assertThat(issues.get(0).getName()).isEqualTo("/form"); + assertThat(issues.get(1).getIn()).isEqualTo(InEnum.HEADER); + assertThat(issues.get(1).getName()).isEqualTo("cookie"); assertThat(issues.get(2).getIn()).isEqualTo(InEnum.HEADER); assertThat(issues.get(2).getName()).isEqualTo("header"); assertThat(issues.get(3).getIn()).isEqualTo(InEnum.PATH); diff --git a/belgif-rest-problem-java-ee-server/src/test/java/io/github/belgif/rest/problem/ee/server/jaxrs/BadRequestExceptionMapperTest.java b/belgif-rest-problem-java-ee-server/src/test/java/io/github/belgif/rest/problem/ee/server/jaxrs/BadRequestExceptionMapperTest.java index edb8d41d..1cf626b8 100644 --- a/belgif-rest-problem-java-ee-server/src/test/java/io/github/belgif/rest/problem/ee/server/jaxrs/BadRequestExceptionMapperTest.java +++ b/belgif-rest-problem-java-ee-server/src/test/java/io/github/belgif/rest/problem/ee/server/jaxrs/BadRequestExceptionMapperTest.java @@ -25,14 +25,14 @@ void badRequestException() { @Test void badRequestProblem() { BadRequestProblem cause = new BadRequestProblem( - InputValidationIssues.schemaViolation(InEnum.HEADER, "startDate_gt", "2006-087-01", + InputValidationIssues.schemaViolation(InEnum.HEADER, "startDateGt", "2006-087-01", "date has invalid format")); Response response = mapper.toResponse(new BadRequestException("HTTP 400 Bad Request", cause)); assertThat(response.getEntity()).isInstanceOf(BadRequestProblem.class); BadRequestProblem problem = (BadRequestProblem) response.getEntity(); InputValidationIssue issue = problem.getIssues().get(0); assertThat(issue.getIn()).isEqualTo(InEnum.HEADER); - assertThat(issue.getName()).isEqualTo("startDate_gt"); + assertThat(issue.getName()).isEqualTo("startDateGt"); } @Test @@ -41,13 +41,13 @@ void badRequestProblemEnrichFromMessage() { InputValidationIssues.schemaViolation(null, null, "2006-087-01", "date has invalid format")); Response response = mapper.toResponse(new BadRequestException( "RESTEASY003870: Unable to extract parameter from http request: " - + "jakarta.ws.rs.HeaderParam(\"startDate_gt\") value is '2006-087-01'", + + "jakarta.ws.rs.HeaderParam(\"startDateGt\") value is '2006-087-01'", cause)); assertThat(response.getEntity()).isInstanceOf(BadRequestProblem.class); BadRequestProblem problem = (BadRequestProblem) response.getEntity(); InputValidationIssue issue = problem.getIssues().get(0); assertThat(issue.getIn()).isEqualTo(InEnum.HEADER); - assertThat(issue.getName()).isEqualTo("startDate_gt"); + assertThat(issue.getName()).isEqualTo("startDateGt"); } @Test diff --git a/belgif-rest-problem-java-ee-server/src/test/java/io/github/belgif/rest/problem/ee/server/jaxrs/ConstraintViolationExceptionMapperTest.java b/belgif-rest-problem-java-ee-server/src/test/java/io/github/belgif/rest/problem/ee/server/jaxrs/ConstraintViolationExceptionMapperTest.java index 54cd236f..f5da36db 100644 --- a/belgif-rest-problem-java-ee-server/src/test/java/io/github/belgif/rest/problem/ee/server/jaxrs/ConstraintViolationExceptionMapperTest.java +++ b/belgif-rest-problem-java-ee-server/src/test/java/io/github/belgif/rest/problem/ee/server/jaxrs/ConstraintViolationExceptionMapperTest.java @@ -52,8 +52,8 @@ void sortsIssuesByName() { assertThat(response.getStatus()).isEqualTo(400); assertThat(response.getEntity()).isInstanceOf(BadRequestProblem.class); BadRequestProblem problem = (BadRequestProblem) response.getEntity(); - assertThat(problem.getIssues().get(0).getName()).isEqualTo("first"); - assertThat(problem.getIssues().get(1).getName()).isEqualTo("second"); + assertThat(problem.getIssues().get(0).getName()).isEqualTo("/first"); + assertThat(problem.getIssues().get(1).getName()).isEqualTo("/second"); } } diff --git a/belgif-rest-problem-java-ee-server/src/test/java/io/github/belgif/rest/problem/ee/server/jaxrs/JacksonJsonMappingExceptionMapperTest.java b/belgif-rest-problem-java-ee-server/src/test/java/io/github/belgif/rest/problem/ee/server/jaxrs/JacksonJsonMappingExceptionMapperTest.java index e19dd1a1..5efff176 100644 --- a/belgif-rest-problem-java-ee-server/src/test/java/io/github/belgif/rest/problem/ee/server/jaxrs/JacksonJsonMappingExceptionMapperTest.java +++ b/belgif-rest-problem-java-ee-server/src/test/java/io/github/belgif/rest/problem/ee/server/jaxrs/JacksonJsonMappingExceptionMapperTest.java @@ -30,7 +30,7 @@ void toResponse() { InputValidationIssue issue = problem.getIssues().get(0); assertThat(issue.getType()).hasToString("urn:problem-type:belgif:input-validation:schemaViolation"); assertThat(issue.getIn()).isEqualTo(InEnum.BODY); - assertThat(issue.getName()).isEqualTo("id"); + assertThat(issue.getName()).isEqualTo("/id"); assertThat(issue.getValue()).isNull(); assertThat(issue.getDetail()).isEqualTo("detail"); } @@ -52,7 +52,7 @@ void valueInstantiationExceptionToResponse() { InputValidationIssue issue = problem.getIssues().get(0); assertThat(issue.getType()).hasToString("urn:problem-type:belgif:input-validation:schemaViolation"); assertThat(issue.getIn()).isEqualTo(InEnum.BODY); - assertThat(issue.getName()).isEqualTo("id"); + assertThat(issue.getName()).isEqualTo("/id"); assertThat(issue.getValue()).isNull(); assertThat(issue.getDetail()).isEqualTo("Unexpected value 'XXL'"); } diff --git a/belgif-rest-problem-java-ee-server/src/test/java/io/github/belgif/rest/problem/ee/server/jaxrs/JacksonMismatchedInputExceptionMapperTest.java b/belgif-rest-problem-java-ee-server/src/test/java/io/github/belgif/rest/problem/ee/server/jaxrs/JacksonMismatchedInputExceptionMapperTest.java index 337a820b..08950232 100644 --- a/belgif-rest-problem-java-ee-server/src/test/java/io/github/belgif/rest/problem/ee/server/jaxrs/JacksonMismatchedInputExceptionMapperTest.java +++ b/belgif-rest-problem-java-ee-server/src/test/java/io/github/belgif/rest/problem/ee/server/jaxrs/JacksonMismatchedInputExceptionMapperTest.java @@ -28,7 +28,7 @@ void toResponse() { InputValidationIssue issue = problem.getIssues().get(0); assertThat(issue.getType()).hasToString("urn:problem-type:belgif:input-validation:schemaViolation"); assertThat(issue.getIn()).isEqualTo(InEnum.BODY); - assertThat(issue.getName()).isEqualTo("id"); + assertThat(issue.getName()).isEqualTo("/id"); assertThat(issue.getValue()).isNull(); assertThat(issue.getDetail()).isEqualTo("detail"); } diff --git a/belgif-rest-problem-java-ee-server/src/test/java/io/github/belgif/rest/problem/ee/server/jaxrs/NotFoundExceptionMapperTest.java b/belgif-rest-problem-java-ee-server/src/test/java/io/github/belgif/rest/problem/ee/server/jaxrs/NotFoundExceptionMapperTest.java index 88084694..80b1cc37 100644 --- a/belgif-rest-problem-java-ee-server/src/test/java/io/github/belgif/rest/problem/ee/server/jaxrs/NotFoundExceptionMapperTest.java +++ b/belgif-rest-problem-java-ee-server/src/test/java/io/github/belgif/rest/problem/ee/server/jaxrs/NotFoundExceptionMapperTest.java @@ -26,14 +26,14 @@ void notFoundException() { @Test void badRequestProblem() { BadRequestProblem cause = new BadRequestProblem( - InputValidationIssues.schemaViolation(InEnum.QUERY, "startDate_gt", "2006-087-01", + InputValidationIssues.schemaViolation(InEnum.QUERY, "startDateGt", "2006-087-01", "date has invalid format")); Response response = mapper.toResponse(new NotFoundException("HTTP 404 Not Found", cause)); assertThat(response.getEntity()).isInstanceOf(BadRequestProblem.class); BadRequestProblem problem = (BadRequestProblem) response.getEntity(); InputValidationIssue issue = problem.getIssues().get(0); assertThat(issue.getIn()).isEqualTo(InEnum.QUERY); - assertThat(issue.getName()).isEqualTo("startDate_gt"); + assertThat(issue.getName()).isEqualTo("startDateGt"); } @Test @@ -42,13 +42,13 @@ void badRequestProblemEnrichFromMessage() { InputValidationIssues.schemaViolation(null, null, "2006-087-01", "date has invalid format")); Response response = mapper.toResponse(new NotFoundException( "RESTEASY003870: Unable to extract parameter from http request: " - + "jakarta.ws.rs.QueryParam(\"startDate_gt\") value is '2006-087-01'", + + "jakarta.ws.rs.QueryParam(\"startDateGt\") value is '2006-087-01'", cause)); assertThat(response.getEntity()).isInstanceOf(BadRequestProblem.class); BadRequestProblem problem = (BadRequestProblem) response.getEntity(); InputValidationIssue issue = problem.getIssues().get(0); assertThat(issue.getIn()).isEqualTo(InEnum.QUERY); - assertThat(issue.getName()).isEqualTo("startDate_gt"); + assertThat(issue.getName()).isEqualTo("startDateGt"); } @Test diff --git a/belgif-rest-problem-quarkus-core/src/main/java/io/github/belgif/rest/problem/quarkus/core/QuarkusProblemConfigurator.java b/belgif-rest-problem-quarkus-core/src/main/java/io/github/belgif/rest/problem/quarkus/core/QuarkusProblemConfigurator.java index 27b4e4eb..dc01e2f3 100644 --- a/belgif-rest-problem-quarkus-core/src/main/java/io/github/belgif/rest/problem/quarkus/core/QuarkusProblemConfigurator.java +++ b/belgif-rest-problem-quarkus-core/src/main/java/io/github/belgif/rest/problem/quarkus/core/QuarkusProblemConfigurator.java @@ -26,11 +26,14 @@ public QuarkusProblemConfigurator( @ConfigProperty( name = ProblemConfig.PROPERTY_EXT_ISSUE_TYPES_ENABLED) Optional extIssueTypesEnabled, @ConfigProperty( - name = ProblemConfig.PROPERTY_EXT_INPUTS_ARRAY_ENABLED) Optional extInputsArrayEnabled) { + name = ProblemConfig.PROPERTY_EXT_INPUTS_ARRAY_ENABLED) Optional extInputsArrayEnabled, + @ConfigProperty( + name = ProblemConfig.PROPERTY_JSON_POINTER_ENABLED) Optional jsonPointerEnabled) { i18nEnabled.ifPresent(ProblemConfig::setI18nEnabled); stackTraceEnabled.ifPresent(ProblemConfig::setStackTraceEnabled); extIssueTypesEnabled.ifPresent(ProblemConfig::setExtIssueTypesEnabled); extInputsArrayEnabled.ifPresent(ProblemConfig::setExtInputsArrayEnabled); + jsonPointerEnabled.ifPresent(ProblemConfig::setJsonPointerEnabled); } } diff --git a/belgif-rest-problem-quarkus-core/src/test/java/io/github/belgif/rest/problem/quarkus/core/QuarkusProblemConfiguratorTest.java b/belgif-rest-problem-quarkus-core/src/test/java/io/github/belgif/rest/problem/quarkus/core/QuarkusProblemConfiguratorTest.java index e2c0a187..c0eceb04 100644 --- a/belgif-rest-problem-quarkus-core/src/test/java/io/github/belgif/rest/problem/quarkus/core/QuarkusProblemConfiguratorTest.java +++ b/belgif-rest-problem-quarkus-core/src/test/java/io/github/belgif/rest/problem/quarkus/core/QuarkusProblemConfiguratorTest.java @@ -20,20 +20,23 @@ void cleanup() { @Test void allEnabled() { - new QuarkusProblemConfigurator(Optional.of(true), Optional.of(true), Optional.of(true), Optional.of(true)); + new QuarkusProblemConfigurator(Optional.of(true), Optional.of(true), Optional.of(true), Optional.of(true), + Optional.of(true)); assertThat(ProblemConfig.isI18nEnabled()).isTrue(); assertThat(ProblemConfig.isStackTraceEnabled()).isTrue(); assertThat(ProblemConfig.isExtIssueTypesEnabled()).isTrue(); assertThat(ProblemConfig.isExtInputsArrayEnabled()).isTrue(); + assertThat(ProblemConfig.isJsonPointerEnabled()).isTrue(); } @Test void allDisabled() { - new QuarkusProblemConfigurator(Optional.of(false), Optional.of(false), Optional.of(false), Optional.of(false)); + new QuarkusProblemConfigurator(Optional.of(false), Optional.of(false), Optional.of(false), Optional.of(false), + Optional.of(false)); assertThat(ProblemConfig.isI18nEnabled()).isFalse(); assertThat(ProblemConfig.isStackTraceEnabled()).isFalse(); assertThat(ProblemConfig.isExtIssueTypesEnabled()).isFalse(); - assertThat(ProblemConfig.isExtInputsArrayEnabled()).isFalse(); + assertThat(ProblemConfig.isJsonPointerEnabled()).isFalse(); } @Test @@ -42,11 +45,14 @@ void notConfigured() { boolean stackTraceEnabledBefore = ProblemConfig.isStackTraceEnabled(); boolean extIssueTypesEnabledBefore = ProblemConfig.isExtIssueTypesEnabled(); boolean extInputsArrayEnabledBefore = ProblemConfig.isExtInputsArrayEnabled(); - new QuarkusProblemConfigurator(Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty()); + boolean jsonPointerEnabledBefore = ProblemConfig.isJsonPointerEnabled(); + new QuarkusProblemConfigurator(Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), + Optional.empty()); assertThat(ProblemConfig.isI18nEnabled()).isEqualTo(i18nEnabledBefore); assertThat(ProblemConfig.isStackTraceEnabled()).isEqualTo(stackTraceEnabledBefore); assertThat(ProblemConfig.isExtIssueTypesEnabled()).isEqualTo(extIssueTypesEnabledBefore); assertThat(ProblemConfig.isExtInputsArrayEnabled()).isEqualTo(extInputsArrayEnabledBefore); + assertThat(ProblemConfig.isJsonPointerEnabled()).isEqualTo(jsonPointerEnabledBefore); } } diff --git a/belgif-rest-problem-spring/src/main/java/io/github/belgif/rest/problem/spring/ProblemConfigurationProperties.java b/belgif-rest-problem-spring/src/main/java/io/github/belgif/rest/problem/spring/ProblemConfigurationProperties.java index c104f59f..5a40592e 100644 --- a/belgif-rest-problem-spring/src/main/java/io/github/belgif/rest/problem/spring/ProblemConfigurationProperties.java +++ b/belgif-rest-problem-spring/src/main/java/io/github/belgif/rest/problem/spring/ProblemConfigurationProperties.java @@ -21,6 +21,8 @@ public class ProblemConfigurationProperties implements InitializingBean { private Boolean stackTraceEnabled = null; + private Boolean jsonPointerEnabled = null; + @Value("${io.github.belgif.rest.problem.scan-additional-problem-packages:#{{}}}") public void setScanAdditionalProblemPackages(List scanAdditionalProblemPackages) { this.scanAdditionalProblemPackages = scanAdditionalProblemPackages; @@ -40,6 +42,11 @@ public void setStackTraceEnabled(Boolean stackTraceEnabled) { this.stackTraceEnabled = stackTraceEnabled; } + @Value("${io.github.belgif.rest.problem.json-pointer-enabled:#{null}}") + public void setJsonPointerEnabled(Boolean jsonPointerEnabled) { + this.jsonPointerEnabled = jsonPointerEnabled; + } + @Override public void afterPropertiesSet() { if (i18nEnabled != null) { @@ -48,6 +55,10 @@ public void afterPropertiesSet() { if (stackTraceEnabled != null) { ProblemConfig.setStackTraceEnabled(stackTraceEnabled); } + + if (jsonPointerEnabled != null) { + ProblemConfig.setJsonPointerEnabled(jsonPointerEnabled); + } } } diff --git a/belgif-rest-problem-spring/src/main/java/io/github/belgif/rest/problem/spring/server/BeanValidationExceptionsHandler.java b/belgif-rest-problem-spring/src/main/java/io/github/belgif/rest/problem/spring/server/BeanValidationExceptionsHandler.java index 9dd5ab7e..c4098cf3 100644 --- a/belgif-rest-problem-spring/src/main/java/io/github/belgif/rest/problem/spring/server/BeanValidationExceptionsHandler.java +++ b/belgif-rest-problem-spring/src/main/java/io/github/belgif/rest/problem/spring/server/BeanValidationExceptionsHandler.java @@ -84,8 +84,8 @@ public ResponseEntity handleBindException(BindException exception, Serv : name + " of incorrect type"; String invalidValue = (String) exception.getValue(); return ProblemMediaType.INSTANCE - .toResponse(new BadRequestProblem(InputValidationIssues.schemaViolation(in, name, invalidValue, - detail))); + .toResponse(new BadRequestProblem(InputValidationIssues.schemaViolation(in, + InputValidationIssue.transformName(in, name), invalidValue, detail))); } @ExceptionHandler(HandlerMethodValidationException.class) @@ -120,7 +120,8 @@ public void modelAttribute(@Nullable ModelAttribute modelAttribute, ParameterErr if (modelAttribute != null) { errors.getResolvableErrors().forEach(error -> issues.add( InputValidationIssues.schemaViolation( - InEnum.BODY, modelAttribute.value(), errors.getArgument(), + InEnum.BODY, InputValidationIssue.transformName(InEnum.BODY, modelAttribute.value()), + errors.getArgument(), error.getDefaultMessage()))); } } @@ -169,7 +170,8 @@ public void requestParam(@Nullable RequestParam requestParam, ParameterValidatio public void requestPart(RequestPart requestPart, ParameterErrors errors) { errors.getResolvableErrors().forEach(error -> issues.add( InputValidationIssues.schemaViolation( - InEnum.BODY, requestPart.value(), errors.getArgument(), + InEnum.BODY, InputValidationIssue.transformName(InEnum.BODY, requestPart.value()), + errors.getArgument(), error.getDefaultMessage()))); } diff --git a/belgif-rest-problem-spring/src/main/java/io/github/belgif/rest/problem/spring/server/internal/BeanValidationExceptionUtil.java b/belgif-rest-problem-spring/src/main/java/io/github/belgif/rest/problem/spring/server/internal/BeanValidationExceptionUtil.java index a76fee7c..5cf769ee 100644 --- a/belgif-rest-problem-spring/src/main/java/io/github/belgif/rest/problem/spring/server/internal/BeanValidationExceptionUtil.java +++ b/belgif-rest-problem-spring/src/main/java/io/github/belgif/rest/problem/spring/server/internal/BeanValidationExceptionUtil.java @@ -3,7 +3,6 @@ import java.util.Iterator; import java.util.LinkedList; import java.util.Objects; -import java.util.stream.Collectors; import jakarta.validation.ConstraintViolation; import jakarta.validation.ElementKind; @@ -45,13 +44,18 @@ public static InputValidationIssue convertToInputValidationIssue(ConstraintViola } } InEnum in = DetermineSourceUtil.determineSource(violation, propertyPath, methodNode); - String name = propertyPath.stream().map(Node::toString).collect(Collectors.joining(".")); + + String name = + InputValidationIssue.getNameFromProperties(in, propertyPath.stream().map(Node::toString).toList()); + return InputValidationIssues.schemaViolation(in, name, violation.getInvalidValue(), violation.getMessage()); } public static InputValidationIssue convertToInputValidationIssue(@NotNull FieldError fieldError, InEnum in) { String invalidValue = Objects.toString(fieldError.getRejectedValue(), null); - return InputValidationIssues.schemaViolation(in, fieldError.getField(), invalidValue, + String name = fieldError.getField(); + + return InputValidationIssues.schemaViolation(in, InputValidationIssue.transformName(in, name), invalidValue, fieldError.getDefaultMessage()); } diff --git a/belgif-rest-problem-spring/src/test/java/io/github/belgif/rest/problem/spring/ProblemConfigurationPropertiesTest.java b/belgif-rest-problem-spring/src/test/java/io/github/belgif/rest/problem/spring/ProblemConfigurationPropertiesTest.java index a8125c15..3f7e8a15 100644 --- a/belgif-rest-problem-spring/src/test/java/io/github/belgif/rest/problem/spring/ProblemConfigurationPropertiesTest.java +++ b/belgif-rest-problem-spring/src/test/java/io/github/belgif/rest/problem/spring/ProblemConfigurationPropertiesTest.java @@ -25,10 +25,12 @@ void cleanup() { void empty() { boolean i18nEnabledBefore = ProblemConfig.isI18nEnabled(); boolean stackTraceEnabledBefore = ProblemConfig.isStackTraceEnabled(); + boolean jsonPointerEnabledBefore = ProblemConfig.isJsonPointerEnabled(); properties.afterPropertiesSet(); assertThat(properties.getScanAdditionalProblemPackages()).isEmpty(); assertThat(ProblemConfig.isI18nEnabled()).isEqualTo(i18nEnabledBefore); assertThat(ProblemConfig.isStackTraceEnabled()).isEqualTo(stackTraceEnabledBefore); + assertThat(ProblemConfig.isJsonPointerEnabled()).isEqualTo(jsonPointerEnabledBefore); } @Test @@ -66,4 +68,17 @@ void stackTraceDisabled() { assertThat(ProblemConfig.isStackTraceEnabled()).isFalse(); } + @Test + void jsonPointerEnabled() { + properties.setJsonPointerEnabled(true); + properties.afterPropertiesSet(); + assertThat(ProblemConfig.isJsonPointerEnabled()).isTrue(); + } + + @Test + void jsonPointerDisabled() { + properties.setJsonPointerEnabled(false); + properties.afterPropertiesSet(); + assertThat(ProblemConfig.isJsonPointerEnabled()).isFalse(); + } } diff --git a/belgif-rest-problem-spring/src/test/java/io/github/belgif/rest/problem/spring/server/AbstractRoutingExceptionsHandlerTest.java b/belgif-rest-problem-spring/src/test/java/io/github/belgif/rest/problem/spring/server/AbstractRoutingExceptionsHandlerTest.java index e3f39da2..4963b8f0 100644 --- a/belgif-rest-problem-spring/src/test/java/io/github/belgif/rest/problem/spring/server/AbstractRoutingExceptionsHandlerTest.java +++ b/belgif-rest-problem-spring/src/test/java/io/github/belgif/rest/problem/spring/server/AbstractRoutingExceptionsHandlerTest.java @@ -98,7 +98,7 @@ void handleHttpMessageNotReadableJacksonMismatchedInputException() { assertThat(problem.getIssues().get(0).getType()) .hasToString("urn:problem-type:belgif:input-validation:schemaViolation"); assertThat(problem.getIssues().get(0).getIn()).isEqualTo(InEnum.BODY); - assertThat(problem.getIssues().get(0).getName()).isEqualTo("id"); + assertThat(problem.getIssues().get(0).getName()).isEqualTo("/id"); assertThat(problem.getIssues().get(0).getValue()).isNull(); assertThat(problem.getIssues().get(0).getDetail()).isEqualTo("detail"); }); diff --git a/belgif-rest-problem-spring/src/test/java/io/github/belgif/rest/problem/spring/server/BeanValidationExceptionsHandlerTest.java b/belgif-rest-problem-spring/src/test/java/io/github/belgif/rest/problem/spring/server/BeanValidationExceptionsHandlerTest.java index 96ec2b49..8b86e232 100644 --- a/belgif-rest-problem-spring/src/test/java/io/github/belgif/rest/problem/spring/server/BeanValidationExceptionsHandlerTest.java +++ b/belgif-rest-problem-spring/src/test/java/io/github/belgif/rest/problem/spring/server/BeanValidationExceptionsHandlerTest.java @@ -65,8 +65,8 @@ void handleConstraintViolationException() { assertThat(entity.getHeaders().getContentType()).isEqualTo(ProblemMediaType.INSTANCE); assertThat(entity.getBody()).isInstanceOfSatisfying(BadRequestProblem.class, problem -> { assertThat(problem.getIssues()).hasSize(2); - assertThat(problem.getIssues().get(0).getName()).isEqualTo("first"); - assertThat(problem.getIssues().get(1).getName()).isEqualTo("second"); + assertThat(problem.getIssues().get(0).getName()).isEqualTo("/first"); + assertThat(problem.getIssues().get(1).getName()).isEqualTo("/second"); }); } @@ -87,11 +87,11 @@ void handleMethodArgumentNotValidException() throws Exception { assertThat(entity.getBody()).isInstanceOfSatisfying(BadRequestProblem.class, problem -> { assertThat(problem.getIssues()).hasSize(2); assertThat(problem.getIssues().get(0).getIn()).isEqualTo(InEnum.BODY); - assertThat(problem.getIssues().get(0).getName()).isEqualTo("first"); + assertThat(problem.getIssues().get(0).getName()).isEqualTo("/first"); assertThat(problem.getIssues().get(0).getValue()).isEqualTo("firstValue"); assertThat(problem.getIssues().get(0).getDetail()).isEqualTo("firstDetail"); assertThat(problem.getIssues().get(1).getIn()).isEqualTo(InEnum.BODY); - assertThat(problem.getIssues().get(1).getName()).isEqualTo("second"); + assertThat(problem.getIssues().get(1).getName()).isEqualTo("/second"); assertThat(problem.getIssues().get(1).getValue()).isEqualTo("secondValue"); assertThat(problem.getIssues().get(1).getDetail()).isEqualTo("secondDetail"); }); @@ -244,7 +244,7 @@ void handlerMethodValidationExceptionVisitorModelAttribute() { assertThat(issues).hasSize(1); assertThat(issues.get(0).getType()).isEqualTo(InputValidationIssues.ISSUE_TYPE_SCHEMA_VIOLATION); assertThat(issues.get(0).getIn()).isEqualTo(InEnum.BODY); - assertThat(issues.get(0).getName()).isEqualTo("name"); + assertThat(issues.get(0).getName()).isEqualTo("/name"); assertThat(issues.get(0).getValue()).isEqualTo("value"); assertThat(issues.get(0).getDetail()).isEqualTo("message"); } @@ -374,7 +374,7 @@ void handlerMethodValidationExceptionVisitorRequestPart() { assertThat(issues).hasSize(1); assertThat(issues.get(0).getType()).isEqualTo(InputValidationIssues.ISSUE_TYPE_SCHEMA_VIOLATION); assertThat(issues.get(0).getIn()).isEqualTo(InEnum.BODY); - assertThat(issues.get(0).getName()).isEqualTo("name"); + assertThat(issues.get(0).getName()).isEqualTo("/name"); assertThat(issues.get(0).getValue()).isEqualTo("value"); assertThat(issues.get(0).getDetail()).isEqualTo("message"); } diff --git a/belgif-rest-problem-spring/src/test/java/io/github/belgif/rest/problem/spring/server/internal/BeanValidationExceptionUtilTest.java b/belgif-rest-problem-spring/src/test/java/io/github/belgif/rest/problem/spring/server/internal/BeanValidationExceptionUtilTest.java index 7ec42ec8..c6f51567 100644 --- a/belgif-rest-problem-spring/src/test/java/io/github/belgif/rest/problem/spring/server/internal/BeanValidationExceptionUtilTest.java +++ b/belgif-rest-problem-spring/src/test/java/io/github/belgif/rest/problem/spring/server/internal/BeanValidationExceptionUtilTest.java @@ -14,6 +14,7 @@ import jakarta.validation.constraints.NotNull; import org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.validation.FieldError; import org.springframework.web.bind.annotation.PathVariable; @@ -23,6 +24,7 @@ import io.github.belgif.rest.problem.api.InEnum; import io.github.belgif.rest.problem.api.InputValidationIssue; import io.github.belgif.rest.problem.api.InputValidationIssues; +import io.github.belgif.rest.problem.config.ProblemConfig; class BeanValidationExceptionUtilTest { @@ -33,6 +35,11 @@ class BeanValidationExceptionUtilTest { .buildValidatorFactory() .getValidator(); + @BeforeEach + void resetProblemConfig() { + ProblemConfig.reset(); + } + @Test void constraintViolationBodyProperty() { Body target = new Body(); @@ -41,6 +48,24 @@ void constraintViolationBodyProperty() { Set> violations = validator.validate(target); assertThat(violations).hasSize(1); + InputValidationIssue issue = + BeanValidationExceptionUtil.convertToInputValidationIssue(violations.iterator().next()); + assertThat(issue.getIn()).isEqualTo(InEnum.BODY); + assertThat(issue.getName()).isEqualTo("/value"); + assertThat(issue.getValue()).isEqualTo(10); + assertThat(issue.getDetail()).isEqualTo("must be less than or equal to 5"); + } + + @Test + void constraintViolationBodyPropertyWithJsonPointerDisabled() { + ProblemConfig.setJsonPointerEnabled(false); + + Body target = new Body(); + target.value = 10; + + Set> violations = validator.validate(target); + assertThat(violations).hasSize(1); + InputValidationIssue issue = BeanValidationExceptionUtil.convertToInputValidationIssue(violations.iterator().next()); assertThat(issue.getIn()).isEqualTo(InEnum.BODY); @@ -58,6 +83,25 @@ void constraintViolationNestedBodyProperty() { Set> violations = validator.validate(target); assertThat(violations).hasSize(1); + InputValidationIssue issue = + BeanValidationExceptionUtil.convertToInputValidationIssue(violations.iterator().next()); + assertThat(issue.getIn()).isEqualTo(InEnum.BODY); + assertThat(issue.getName()).isEqualTo("/nested/1/prop"); + assertThat(issue.getValue()).isNull(); + assertThat(issue.getDetail()).isEqualTo("must not be null"); + } + + @Test + void constraintViolationNestedBodyPropertyWithJsonPointerDisabled() { + ProblemConfig.setJsonPointerEnabled(false); + + Body target = new Body(); + target.nested.add(new Nested("OK")); + target.nested.add(new Nested(null)); + + Set> violations = validator.validate(target); + assertThat(violations).hasSize(1); + InputValidationIssue issue = BeanValidationExceptionUtil.convertToInputValidationIssue(violations.iterator().next()); assertThat(issue.getIn()).isEqualTo(InEnum.BODY); diff --git a/belgif-rest-problem-validator/src/main/java/io/github/belgif/rest/problem/validation/AbstractRequestValidator.java b/belgif-rest-problem-validator/src/main/java/io/github/belgif/rest/problem/validation/AbstractRequestValidator.java index 4db9fb0b..e78fd879 100644 --- a/belgif-rest-problem-validator/src/main/java/io/github/belgif/rest/problem/validation/AbstractRequestValidator.java +++ b/belgif-rest-problem-validator/src/main/java/io/github/belgif/rest/problem/validation/AbstractRequestValidator.java @@ -1,5 +1,7 @@ package io.github.belgif.rest.problem.validation; +import static io.github.belgif.rest.problem.api.InputValidationIssue.*; + import java.time.LocalDate; import java.time.temporal.Temporal; import java.util.ArrayList; @@ -13,6 +15,7 @@ import java.util.stream.Collectors; import io.github.belgif.rest.problem.BadRequestProblem; +import io.github.belgif.rest.problem.api.InEnum; import io.github.belgif.rest.problem.api.Input; import io.github.belgif.rest.problem.api.InputValidationIssue; import io.github.belgif.rest.problem.config.ProblemConfig; @@ -116,7 +119,8 @@ public SELF ssins(Input> ssins) { if (ssins != null && ssins.getValue() != null) { int index = 0; for (String ssin : ssins.getValue()) { - ssin(new Input<>(ssins.getIn(), ssins.getName() + "[" + index + "]", ssin)); + String name = transformName(ssins.getIn(), ssins.getName() + getIndexFormat(ssins.getIn(), index)); + ssin(new Input<>(ssins.getIn(), name, ssin)); index++; } } @@ -359,7 +363,8 @@ public SELF refDatas(Input> input, Supplier> allowedRe Collection allowedRefData = allowedRefDataSupplier.get(); int index = 0; for (T value : input.getValue()) { - refData(new Input(input.getIn(), input.getName() + "[" + index + "]", value), allowedRefData); + String name = transformName(input.getIn(), input.getName() + getIndexFormat(input.getIn(), index)); + refData(new Input(input.getIn(), name, value), allowedRefData); index++; } } @@ -378,8 +383,8 @@ public SELF refDatas(Input> input, Predicate allowedRefDataPredic if (input != null && input.getValue() != null && !input.getValue().isEmpty()) { int index = 0; for (T value : input.getValue()) { - refData(new Input(input.getIn(), input.getName() + "[" + index + "]", value), - allowedRefDataPredicate); + String name = transformName(input.getIn(), input.getName() + getIndexFormat(input.getIn(), index)); + refData(new Input(input.getIn(), name, value), allowedRefDataPredicate); index++; } } @@ -514,4 +519,8 @@ protected SELF getThis() { return (SELF) this; } + private String getIndexFormat(InEnum in, int index) { + return ProblemConfig.isJsonPointerEnabled() && in == InEnum.BODY ? ("/" + index) : ("[" + index + "]"); + } + } diff --git a/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/EmployerIdValidatorTest.java b/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/EmployerIdValidatorTest.java index 01dcbfcb..d5c493fe 100644 --- a/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/EmployerIdValidatorTest.java +++ b/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/EmployerIdValidatorTest.java @@ -14,26 +14,26 @@ class EmployerIdValidatorTest { @ParameterizedTest @ValueSource(longs = { 100006L, 212345609L, 312345625L, 200000031L, 499999982L, 187995796L, 168676597L }) void okNssoNumber(Long employerId) { - assertThat(new EmployerIdValidator(Input.body("test", employerId)).validate()).isEmpty(); + assertThat(new EmployerIdValidator(Input.body("/test", employerId)).validate()).isEmpty(); } @ParameterizedTest @ValueSource(longs = { 5134794036L, 5000000120L, 5999999989L, 5678901277L }) void okProvisionalNssoNumber(Long employerId) { - assertThat(new EmployerIdValidator(Input.body("test", employerId)).validate()).isEmpty(); + assertThat(new EmployerIdValidator(Input.body("/test", employerId)).validate()).isEmpty(); } @ParameterizedTest @ValueSource(longs = { 43220065L, 2130057L, 22300094L, 5170096L, 55290097L }) void okPplNumber(Long employerId) { - assertThat(new EmployerIdValidator(Input.body("test", employerId)).validate()).isEmpty(); + assertThat(new EmployerIdValidator(Input.body("/test", employerId)).validate()).isEmpty(); } @ParameterizedTest @ValueSource(longs = { 193L, 196L, 4000000100L, 6999999999L, 5678901279L, 1000000047L, 5000000121L, 6000000086L }) void nok(Long employerId) { - assertThat(new EmployerIdValidator(Input.body("test", employerId)).validate()) - .contains(InputValidationIssues.invalidEmployerId(BODY, "test", employerId)); + assertThat(new EmployerIdValidator(Input.body("/test", employerId)).validate()) + .contains(InputValidationIssues.invalidEmployerId(BODY, "/test", employerId)); } } diff --git a/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/EnterpriseNumberValidatorTest.java b/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/EnterpriseNumberValidatorTest.java index 8e09d64e..76abc33e 100644 --- a/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/EnterpriseNumberValidatorTest.java +++ b/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/EnterpriseNumberValidatorTest.java @@ -14,14 +14,14 @@ class EnterpriseNumberValidatorTest { @Test void ok() { - assertThat(new EnterpriseNumberValidator(Input.body("test", "0884303369")).validate()).isEmpty(); + assertThat(new EnterpriseNumberValidator(Input.body("/test", "0884303369")).validate()).isEmpty(); } @ParameterizedTest @ValueSource(strings = { "test", "54321", "20000000032", "2111111111", "0884303370" }) void nok(String value) { - assertThat(new EnterpriseNumberValidator(Input.body("test", value)).validate()) - .contains(InputValidationIssues.invalidEnterpriseNumber(BODY, "test", value)); + assertThat(new EnterpriseNumberValidator(Input.body("/test", value)).validate()) + .contains(InputValidationIssues.invalidEnterpriseNumber(BODY, "/test", value)); } } diff --git a/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/EqualValidatorTest.java b/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/EqualValidatorTest.java index 3b51fa03..a381a53c 100644 --- a/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/EqualValidatorTest.java +++ b/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/EqualValidatorTest.java @@ -14,19 +14,19 @@ class EqualValidatorTest { @Test void ok() { - assertThat(new EqualValidator(Arrays.asList(Input.header("id", "25"), Input.body("id", "25"))) + assertThat(new EqualValidator(Arrays.asList(Input.header("id", "25"), Input.body("/id", "25"))) .validate()).isEmpty(); } @Test void okNull() { - assertThat(new EqualValidator(Arrays.asList(Input.header("id", null), Input.body("id", null))) + assertThat(new EqualValidator(Arrays.asList(Input.header("id", null), Input.body("/id", null))) .validate()).isEmpty(); } @Test void nok() { - List> items = Arrays.asList(Input.header("id", "25"), Input.body("id", "26")); + List> items = Arrays.asList(Input.header("id", "25"), Input.body("/id", "26")); assertThat(new EqualValidator(items).validate()).contains(InputValidationIssues.equalExpected(items)); } diff --git a/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/EstablishmentUnitNumberValidatorTest.java b/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/EstablishmentUnitNumberValidatorTest.java index ac073738..dda0e198 100644 --- a/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/EstablishmentUnitNumberValidatorTest.java +++ b/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/EstablishmentUnitNumberValidatorTest.java @@ -14,14 +14,14 @@ class EstablishmentUnitNumberValidatorTest { @Test void ok() { - assertThat(new EstablishmentUnitNumberValidator(Input.body("test", "2297964444")).validate()).isEmpty(); + assertThat(new EstablishmentUnitNumberValidator(Input.body("/test", "2297964444")).validate()).isEmpty(); } @ParameterizedTest @ValueSource(strings = { "test", "54321", "0884303369", "2111111111", "2297964445" }) void nok(String value) { - assertThat(new EstablishmentUnitNumberValidator(Input.body("test", value)).validate()) - .contains(InputValidationIssues.invalidEstablishmentUnitNumber(BODY, "test", value)); + assertThat(new EstablishmentUnitNumberValidator(Input.body("/test", value)).validate()) + .contains(InputValidationIssues.invalidEstablishmentUnitNumber(BODY, "/test", value)); } } diff --git a/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/ExactlyOneOfValidatorTest.java b/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/ExactlyOneOfValidatorTest.java index b456b93b..c6a85ef2 100644 --- a/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/ExactlyOneOfValidatorTest.java +++ b/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/ExactlyOneOfValidatorTest.java @@ -14,22 +14,22 @@ class ExactlyOneOfValidatorTest { @Test void ok() { - assertThat(new ExactlyOneOfValidator(Arrays.asList(Input.body("cbeNumber", null), Input.body("sector", "25"))) + assertThat(new ExactlyOneOfValidator(Arrays.asList(Input.body("/cbeNumber", null), Input.body("/sector", "25"))) .validate()).isEmpty(); } @Test void nokMoreThanOne() { - List> items = Arrays.asList(Input.body("cbeNumber", "0694965804"), - Input.body("sector", "25")); + List> items = Arrays.asList(Input.body("/cbeNumber", "0694965804"), + Input.body("/sector", "25")); assertThat(new ExactlyOneOfValidator(items).validate()) .contains(InputValidationIssues.exactlyOneOfExpected(items)); } @Test void nokNone() { - List> items = Arrays.asList(Input.body("cbeNumber", null), - Input.body("sector", null)); + List> items = Arrays.asList(Input.body("/cbeNumber", null), + Input.body("/sector", null)); assertThat(new ExactlyOneOfValidator(items).validate()) .contains(InputValidationIssues.exactlyOneOfExpected(items)); } diff --git a/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/IncompleteDateValidatorTest.java b/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/IncompleteDateValidatorTest.java index 7f89f6c2..c0cc13b8 100644 --- a/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/IncompleteDateValidatorTest.java +++ b/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/IncompleteDateValidatorTest.java @@ -14,43 +14,43 @@ class IncompleteDateValidatorTest { @Test void okIncomplete() { - assertThat(new IncompleteDateValidator(Input.body("test", "2024-01-00")).validate()).isEmpty(); + assertThat(new IncompleteDateValidator(Input.body("/test", "2024-01-00")).validate()).isEmpty(); } @Test void okComplete() { - assertThat(new IncompleteDateValidator(Input.body("test", "2024-01-01")).validate()).isEmpty(); + assertThat(new IncompleteDateValidator(Input.body("/test", "2024-01-01")).validate()).isEmpty(); } @Test void nokMonthOutOfRange() { - assertThat(new IncompleteDateValidator(Input.body("test", "2024-13-01")).validate()) - .contains(InputValidationIssues.invalidIncompleteDate(BODY, "test", "2024-13-01")); + assertThat(new IncompleteDateValidator(Input.body("/test", "2024-13-01")).validate()) + .contains(InputValidationIssues.invalidIncompleteDate(BODY, "/test", "2024-13-01")); } @Test void nokDateWithoutMonth() { - assertThat(new IncompleteDateValidator(Input.body("test", "2024-00-04")).validate()) - .contains(InputValidationIssues.invalidIncompleteDate(BODY, "test", "2024-00-04")); + assertThat(new IncompleteDateValidator(Input.body("/test", "2024-00-04")).validate()) + .contains(InputValidationIssues.invalidIncompleteDate(BODY, "/test", "2024-00-04")); } @Test void nokDayOutOfRange() { - assertThat(new IncompleteDateValidator(Input.body("test", "2024-02-31")).validate()) - .contains(InputValidationIssues.invalidIncompleteDate(BODY, "test", "2024-02-31")); + assertThat(new IncompleteDateValidator(Input.body("/test", "2024-02-31")).validate()) + .contains(InputValidationIssues.invalidIncompleteDate(BODY, "/test", "2024-02-31")); } @Test void nokInvalidLocalDate() { - assertThat(new IncompleteDateValidator(Input.body("test", "2023-02-29")).validate()) - .contains(InputValidationIssues.invalidIncompleteDate(BODY, "test", "2023-02-29")); + assertThat(new IncompleteDateValidator(Input.body("/test", "2023-02-29")).validate()) + .contains(InputValidationIssues.invalidIncompleteDate(BODY, "/test", "2023-02-29")); } @ParameterizedTest @ValueSource(strings = { "test", "9999-99-99", "2024/01/01" }) void nokPattern(String value) { - assertThat(new IncompleteDateValidator(Input.body("test", value)).validate()) - .contains(InputValidationIssues.invalidIncompleteDate(BODY, "test", value)); + assertThat(new IncompleteDateValidator(Input.body("/test", value)).validate()) + .contains(InputValidationIssues.invalidIncompleteDate(BODY, "/test", value)); } } diff --git a/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/PeriodValidatorTest.java b/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/PeriodValidatorTest.java index 9c94b7bc..2cf6be0d 100644 --- a/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/PeriodValidatorTest.java +++ b/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/PeriodValidatorTest.java @@ -15,35 +15,35 @@ class PeriodValidatorTest { @Test void ok() { // only endDate - assertThat(new PeriodValidator(Input.body("period", new InputPeriod(null, LocalDate.of(2023, 10, 12)))) + assertThat(new PeriodValidator(Input.body("/period", new InputPeriod(null, LocalDate.of(2023, 10, 12)))) .validate()).isEmpty(); // only startDate - assertThat(new PeriodValidator(Input.body("period", new InputPeriod(LocalDate.of(2023, 10, 12), null))) + assertThat(new PeriodValidator(Input.body("/period", new InputPeriod(LocalDate.of(2023, 10, 12), null))) .validate()).isEmpty(); // startDate == endDate assertThat(new PeriodValidator( - Input.body("period", new InputPeriod(LocalDate.of(2023, 10, 12), LocalDate.of(2023, 10, 12)))) + Input.body("/period", new InputPeriod(LocalDate.of(2023, 10, 12), LocalDate.of(2023, 10, 12)))) .validate()).isEmpty(); // startDate < endDate assertThat(new PeriodValidator( - Input.body("period", new InputPeriod(LocalDate.of(2023, 10, 11), LocalDate.of(2023, 10, 12)))) + Input.body("/period", new InputPeriod(LocalDate.of(2023, 10, 11), LocalDate.of(2023, 10, 12)))) .validate()).isEmpty(); // no startDate and endDate - assertThat(new PeriodValidator(Input.body("criteria.periods.period", new InputPeriod(null, null))) + assertThat(new PeriodValidator(Input.body("/criteria/periods/period", new InputPeriod(null, null))) .validate()).isEmpty(); } @Test void nokStartDateAfterEndDate() { InputPeriod badPeriod = new InputPeriod(LocalDate.of(2023, 10, 14), LocalDate.of(2023, 10, 12)); - assertThat(new PeriodValidator(Input.body("period", badPeriod)).validate()).contains( - InputValidationIssues.invalidPeriod(BODY, "period", badPeriod)); + assertThat(new PeriodValidator(Input.body("/period", badPeriod)).validate()).contains( + InputValidationIssues.invalidPeriod(BODY, "/period", badPeriod)); } @Test void nokInvalidObject() { assertThatIllegalArgumentException().isThrownBy(() -> new PeriodValidator( - Input.body("period", "oops")).validate()) + Input.body("/period", "oops")).validate()) .withMessage("No startDate field with type class java.time.LocalDate was found" + " on class java.lang.String"); } diff --git a/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/RejectedInputValidatorTest.java b/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/RejectedInputValidatorTest.java index f6ae8be0..1db876fc 100644 --- a/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/RejectedInputValidatorTest.java +++ b/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/RejectedInputValidatorTest.java @@ -12,13 +12,13 @@ class RejectedInputValidatorTest { @Test void ok() { - assertThat(new RejectedInputValidator(Input.body("reject", null)).validate()).isEmpty(); + assertThat(new RejectedInputValidator(Input.body("/reject", null)).validate()).isEmpty(); } @Test void nok() { - assertThat(new RejectedInputValidator<>(Input.body("reject", "bad")).validate()) - .contains(InputValidationIssues.rejectedInput(BODY, "reject", "bad")); + assertThat(new RejectedInputValidator<>(Input.body("/reject", "bad")).validate()) + .contains(InputValidationIssues.rejectedInput(BODY, "/reject", "bad")); } } diff --git a/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/RequestValidatorTest.java b/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/RequestValidatorTest.java index 2a153cb6..e867d1ed 100644 --- a/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/RequestValidatorTest.java +++ b/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/RequestValidatorTest.java @@ -9,183 +9,194 @@ import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import io.github.belgif.rest.problem.BadRequestProblem; import io.github.belgif.rest.problem.api.Input; import io.github.belgif.rest.problem.api.InputValidationIssue; import io.github.belgif.rest.problem.api.InputValidationIssues; +import io.github.belgif.rest.problem.config.ProblemConfig; class RequestValidatorTest { + @BeforeEach + void resetProblemConfig() { + ProblemConfig.reset(); + } + @Test void ssinValid() { - assertValid(new RequestValidator().ssin(Input.body("ssin", "00000000196"))); + assertValid(new RequestValidator().ssin(Input.body("/ssin", "00000000196"))); } @Test void ssinNull() { assertValid(new RequestValidator().ssin(null)); - assertValid(new RequestValidator().ssin(Input.body("ssin", null))); + assertValid(new RequestValidator().ssin(Input.body("/ssin", null))); } @Test void ssinInvalid() { assertInvalid( - new RequestValidator().ssin(Input.body("ssin", "22222222222")), - InputValidationIssues.invalidSsin(BODY, "ssin", "22222222222")); + new RequestValidator().ssin(Input.body("/ssin", "22222222222")), + InputValidationIssues.invalidSsin(BODY, "/ssin", "22222222222")); } - @Test - void ssinsInvalid() { + @ParameterizedTest + @ValueSource(booleans = { true, false }) + void ssinsInvalid(boolean jsonPointerEnabled) { + ProblemConfig.setJsonPointerEnabled(jsonPointerEnabled); assertInvalid( - new RequestValidator().ssins(Input.body("ssins", + new RequestValidator().ssins(Input.body(jsonPointerEnabled ? "/ssins" : "ssins", Arrays.asList("00000000196", "11111111111", "00000000295", "22222222222"))), - InputValidationIssues.invalidSsin(BODY, "ssins[1]", "11111111111"), - InputValidationIssues.invalidSsin(BODY, "ssins[3]", "22222222222")); + InputValidationIssues.invalidSsin(BODY, jsonPointerEnabled ? "/ssins/1" : "ssins[1]", "11111111111"), + InputValidationIssues.invalidSsin(BODY, jsonPointerEnabled ? "/ssins/3" : "ssins[3]", "22222222222")); } @Test void enterpriseNumberValid() { - assertValid(new RequestValidator().enterpriseNumber(Input.body("enterpriseNumber", "0884303369"))); + assertValid(new RequestValidator().enterpriseNumber(Input.body("/enterpriseNumber", "0884303369"))); } @Test void enterpriseNumberNull() { assertValid(new RequestValidator().enterpriseNumber(null)); - assertValid(new RequestValidator().enterpriseNumber(Input.body("enterpriseNumber", null))); + assertValid(new RequestValidator().enterpriseNumber(Input.body("/enterpriseNumber", null))); } @Test void enterpriseNumberInvalid() { assertInvalid( - new RequestValidator().enterpriseNumber(Input.body("enterpriseNumber", "2111111112")), - InputValidationIssues.invalidEnterpriseNumber(BODY, "enterpriseNumber", "2111111112")); + new RequestValidator().enterpriseNumber(Input.body("/enterpriseNumber", "2111111112")), + InputValidationIssues.invalidEnterpriseNumber(BODY, "/enterpriseNumber", "2111111112")); } @Test void establishmentUnitNumberValid() { assertValid( - new RequestValidator().establishmentUnitNumber(Input.body("establishmentUnitNumber", "2297964444"))); + new RequestValidator().establishmentUnitNumber(Input.body("/establishmentUnitNumber", "2297964444"))); } @Test void establishmentUnitNumberNull() { assertValid(new RequestValidator().establishmentUnitNumber(null)); - assertValid(new RequestValidator().establishmentUnitNumber(Input.body("establishmentUnitNumber", null))); + assertValid(new RequestValidator().establishmentUnitNumber(Input.body("/establishmentUnitNumber", null))); } @Test void establishmentUnitNumberInvalid() { assertInvalid( - new RequestValidator().establishmentUnitNumber(Input.body("establishmentUnitNumber", "2111111111")), - InputValidationIssues.invalidEstablishmentUnitNumber(BODY, "establishmentUnitNumber", "2111111111")); + new RequestValidator().establishmentUnitNumber(Input.body("/establishmentUnitNumber", "2111111111")), + InputValidationIssues.invalidEstablishmentUnitNumber(BODY, "/establishmentUnitNumber", "2111111111")); } @Test void employerIdValid() { - assertValid(new RequestValidator().employerId(Input.body("employerId", 312345625L))); + assertValid(new RequestValidator().employerId(Input.body("/employerId", 312345625L))); } @Test void employerIdNull() { assertValid(new RequestValidator().employerId(null)); - assertValid(new RequestValidator().employerId(Input.body("employerId", null))); + assertValid(new RequestValidator().employerId(Input.body("/employerId", null))); } @Test void employerIdInvalid() { assertInvalid( - new RequestValidator().employerId(Input.body("employerId", 5678901279L)), - InputValidationIssues.invalidEmployerId(BODY, "employerId", 5678901279L)); + new RequestValidator().employerId(Input.body("/employerId", 5678901279L)), + InputValidationIssues.invalidEmployerId(BODY, "/employerId", 5678901279L)); } @Test void periodValid() { InputPeriod period = new InputPeriod(LocalDate.of(2023, 1, 1), LocalDate.of(2023, 12, 31)); - assertValid(new RequestValidator().period(Input.body("period", period))); + assertValid(new RequestValidator().period(Input.body("/period", period))); } @Test void periodNull() { assertValid(new RequestValidator().period(null)); - assertValid(new RequestValidator().period(Input.body("period", null))); + assertValid(new RequestValidator().period(Input.body("/period", null))); } @Test void periodInvalid() { InputPeriod badPeriod = new InputPeriod(LocalDate.of(2023, 10, 14), LocalDate.of(2023, 10, 12)); - assertInvalid(new RequestValidator().period(Input.body("period", badPeriod)), - InputValidationIssues.invalidPeriod(BODY, "period", badPeriod)); + assertInvalid(new RequestValidator().period(Input.body("/period", badPeriod)), + InputValidationIssues.invalidPeriod(BODY, "/period", badPeriod)); } @Test void temporalPeriodValid() { - assertValid(new RequestValidator().period(Input.body("startDate", LocalDate.of(2023, 1, 1)), - Input.body("endDate", LocalDate.of(2023, 12, 31)))); + assertValid(new RequestValidator().period(Input.body("/startDate", LocalDate.of(2023, 1, 1)), + Input.body("/endDate", LocalDate.of(2023, 12, 31)))); } @Test void temporalPeriodInvalid() { assertInvalid( - new RequestValidator().period(Input.body("startDate", LocalDate.of(2023, 10, 14)), - Input.body("endDate", LocalDate.of(2023, 10, 12))), - InputValidationIssues.invalidPeriod(Input.body("startDate", LocalDate.of(2023, 10, 14)), - Input.body("endDate", LocalDate.of(2023, 10, 12)))); + new RequestValidator().period(Input.body("/startDate", LocalDate.of(2023, 10, 14)), + Input.body("/endDate", LocalDate.of(2023, 10, 12))), + InputValidationIssues.invalidPeriod(Input.body("/startDate", LocalDate.of(2023, 10, 14)), + Input.body("/endDate", LocalDate.of(2023, 10, 12)))); } @Test void incompleteDateValid() { - assertValid(new RequestValidator().incompleteDate(Input.body("date", "2024-00-00"))); + assertValid(new RequestValidator().incompleteDate(Input.body("/date", "2024-00-00"))); } @Test void incompleteDateNull() { assertValid(new RequestValidator().incompleteDate(null)); - assertValid(new RequestValidator().incompleteDate(Input.body("date", null))); + assertValid(new RequestValidator().incompleteDate(Input.body("/date", null))); } @Test void incompleteDateInvalid() { assertInvalid( - new RequestValidator().incompleteDate(Input.body("date", "2024-00-01")), - InputValidationIssues.invalidIncompleteDate(BODY, "date", "2024-00-01")); + new RequestValidator().incompleteDate(Input.body("/date", "2024-00-01")), + InputValidationIssues.invalidIncompleteDate(BODY, "/date", "2024-00-01")); } @Test void yearMonthValid() { - assertValid(new RequestValidator().yearMonth(Input.body("yearMonth", "2024-01"))); + assertValid(new RequestValidator().yearMonth(Input.body("/yearMonth", "2024-01"))); } @Test void yearMonthNull() { assertValid(new RequestValidator().yearMonth(null)); - assertValid(new RequestValidator().yearMonth(Input.body("yearMonth", null))); + assertValid(new RequestValidator().yearMonth(Input.body("/yearMonth", null))); } @Test void yearMonthInvalid() { - assertInvalid(new RequestValidator().yearMonth(Input.body("yearMonth", "2024-99")), - InputValidationIssues.invalidYearMonth(BODY, "yearMonth", "2024-99")); + assertInvalid(new RequestValidator().yearMonth(Input.body("/yearMonth", "2024-99")), + InputValidationIssues.invalidYearMonth(BODY, "/yearMonth", "2024-99")); } @Test void exactlyOneOfValid() { - Input[] inputs = { Input.body("cbeNumber", null), Input.body("sector", "25") }; + Input[] inputs = { Input.body("/cbeNumber", null), Input.body("/sector", "25") }; assertValid(new RequestValidator().exactlyOneOf(inputs)); } @Test void exactlyOneOfInvalidMoreThatOne() { - Input[] inputs = { Input.body("cbeNumber", "0694965804"), Input.body("sector", "25") }; + Input[] inputs = { Input.body("/cbeNumber", "0694965804"), Input.body("/sector", "25") }; assertInvalid(new RequestValidator().exactlyOneOf(inputs), InputValidationIssues.exactlyOneOfExpected(Arrays.asList(inputs))); } @Test void exactlyOneOfInvalidNone() { - Input[] inputs = { Input.body("cbeNumber", null), Input.body("sector", null) }; + Input[] inputs = { Input.body("/cbeNumber", null), Input.body("/sector", null) }; assertInvalid(new RequestValidator().exactlyOneOf(inputs), InputValidationIssues.exactlyOneOfExpected(Arrays.asList(inputs))); } @@ -314,22 +325,28 @@ void refDataPredicateInvalid() { InputValidationIssues.referencedResourceNotFound(QUERY, "refData", "x")); } - @Test - void refDatasCollectionValid() { + @ParameterizedTest + @ValueSource(booleans = { true, false }) + void refDatasCollectionValid(boolean jsonPointerEnabled) { + ProblemConfig.setJsonPointerEnabled(jsonPointerEnabled); assertValid(new RequestValidator().refDatas(Input.query("refDatas", Arrays.asList("a", "b")), Arrays.asList("a", "b", "c"))); } - @Test - void refDatasCollectionInvalid() { + @ParameterizedTest + @ValueSource(booleans = { true, false }) + void refDatasCollectionInvalid(boolean isJsonPointerEnabled) { + ProblemConfig.setJsonPointerEnabled(isJsonPointerEnabled); assertInvalid(new RequestValidator().refDatas(Input.query("refDatas", Arrays.asList("a", "x", "b", "y")), Arrays.asList("a", "b", "c")), InputValidationIssues.referencedResourceNotFound(QUERY, "refDatas[1]", "x"), InputValidationIssues.referencedResourceNotFound(QUERY, "refDatas[3]", "y")); } - @Test - void refDatasSupplierValid() { + @ParameterizedTest + @ValueSource(booleans = { true, false }) + void refDatasSupplierValid(boolean isJsonPointerEnabled) { + ProblemConfig.setJsonPointerEnabled(isJsonPointerEnabled); AtomicInteger calls = new AtomicInteger(0); assertValid(new RequestValidator().refDatas(Input.query("refDatas", Arrays.asList("a", "b")), () -> { @@ -339,8 +356,10 @@ void refDatasSupplierValid() { assertThat(calls).hasValue(1); } - @Test - void refDatasSupplierInvalid() { + @ParameterizedTest + @ValueSource(booleans = { true, false }) + void refDatasSupplierInvalid(boolean isJsonPointerEnabled) { + ProblemConfig.setJsonPointerEnabled(isJsonPointerEnabled); AtomicInteger calls = new AtomicInteger(0); assertInvalid(new RequestValidator().refDatas(Input.query("refDatas", Arrays.asList("a", "x", "b", "y")), () -> { @@ -362,14 +381,18 @@ void refDatasSupplierValidEmpty() { assertThat(calls).hasValue(0); } - @Test - void refDatasPredicateValid() { + @ParameterizedTest + @ValueSource(booleans = { true, false }) + void refDatasPredicateValid(boolean isJsonPointerEnabled) { + ProblemConfig.setJsonPointerEnabled(isJsonPointerEnabled); assertValid(new RequestValidator().refDatas(Input.query("refDatas", Arrays.asList("a", "b")), Arrays.asList("a", "b", "c")::contains)); } - @Test - void refDatasPredicateInvalid() { + @ParameterizedTest + @ValueSource(booleans = { true, false }) + void refDatasPredicateInvalid(boolean isJsonPointerEnabled) { + ProblemConfig.setJsonPointerEnabled(isJsonPointerEnabled); assertInvalid(new RequestValidator().refDatas(Input.query("refDatas", Arrays.asList("a", "x", "b", "y")), Arrays.asList("a", "b", "c")::contains), InputValidationIssues.referencedResourceNotFound(QUERY, "refDatas[1]", "x"), @@ -474,29 +497,29 @@ void customInvalid() { @Test void chainingValid() { - assertValid(new RequestValidator().ssin(Input.body("ssin", "00000000196")) - .enterpriseNumber(Input.body("enterpriseNumber", "0884303369"))); + assertValid(new RequestValidator().ssin(Input.body("/ssin", "00000000196")) + .enterpriseNumber(Input.body("/enterpriseNumber", "0884303369"))); } @Test void chainingInvalid() { - assertInvalid(new RequestValidator().ssin(Input.body("ssin", "22222222222")) - .enterpriseNumber(Input.body("enterpriseNumber", "2111111112")), - InputValidationIssues.invalidSsin(BODY, "ssin", "22222222222"), - InputValidationIssues.invalidEnterpriseNumber(BODY, "enterpriseNumber", "2111111112")); + assertInvalid(new RequestValidator().ssin(Input.body("/ssin", "22222222222")) + .enterpriseNumber(Input.body("/enterpriseNumber", "2111111112")), + InputValidationIssues.invalidSsin(BODY, "/ssin", "22222222222"), + InputValidationIssues.invalidEnterpriseNumber(BODY, "/enterpriseNumber", "2111111112")); } @Test void extension() { new RequestValidatorExtensionB() - .require(Input.body("test", "value")) + .require(Input.body("/test", "value")) .b() .a() .validate(); new RequestValidatorExtensionB() .a() .b() - .require(Input.body("test", "value")) + .require(Input.body("/test", "value")) .validate(); } diff --git a/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/RequiredInputValidatorTest.java b/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/RequiredInputValidatorTest.java index 90a54e8a..530bc21b 100644 --- a/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/RequiredInputValidatorTest.java +++ b/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/RequiredInputValidatorTest.java @@ -12,13 +12,13 @@ class RequiredInputValidatorTest { @Test void ok() { - assertThat(new RequiredInputValidator<>(Input.body("required", "ok")).validate()).isEmpty(); + assertThat(new RequiredInputValidator<>(Input.body("/required", "ok")).validate()).isEmpty(); } @Test void nok() { - assertThat(new RequiredInputValidator(Input.body("required", null)).validate()) - .contains(InputValidationIssues.requiredInput(BODY, "required")); + assertThat(new RequiredInputValidator(Input.body("/required", null)).validate()) + .contains(InputValidationIssues.requiredInput(BODY, "/required")); } } diff --git a/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/SsinValidatorTest.java b/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/SsinValidatorTest.java index deb1c12b..6fad610d 100644 --- a/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/SsinValidatorTest.java +++ b/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/SsinValidatorTest.java @@ -122,12 +122,12 @@ void nokTooShort() { } private void assertValidSsin(String ssin) { - assertThat(new SsinValidator(Input.body("test", ssin)).validate()).isEmpty(); + assertThat(new SsinValidator(Input.body("/test", ssin)).validate()).isEmpty(); } private void assertInvalidSsin(String ssin) { - assertThat(new SsinValidator(Input.body("test", ssin)).validate()) - .contains(InputValidationIssues.invalidSsin(BODY, "test", ssin)); + assertThat(new SsinValidator(Input.body("/test", ssin)).validate()) + .contains(InputValidationIssues.invalidSsin(BODY, "/test", ssin)); } private static String createSsinFromDate(LocalDate date) { diff --git a/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/YearMonthValidatorTest.java b/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/YearMonthValidatorTest.java index 32003189..cb253a61 100644 --- a/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/YearMonthValidatorTest.java +++ b/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/YearMonthValidatorTest.java @@ -12,13 +12,13 @@ class YearMonthValidatorTest { @Test void ok() { - assertThat(new YearMonthValidator(Input.body("test", "2024-01")).validate()).isEmpty(); + assertThat(new YearMonthValidator(Input.body("/test", "2024-01")).validate()).isEmpty(); } @Test void nok() { - assertThat(new YearMonthValidator(Input.body("criteria.month", "2024-99")).validate()) - .contains(InputValidationIssues.invalidYearMonth(BODY, "criteria.month", "2024-99")); + assertThat(new YearMonthValidator(Input.body("/criteria/month", "2024-99")).validate()) + .contains(InputValidationIssues.invalidYearMonth(BODY, "/criteria/month", "2024-99")); } } diff --git a/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/ZeroOrAllOfValidatorTest.java b/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/ZeroOrAllOfValidatorTest.java index ea8b535f..5ecebd06 100644 --- a/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/ZeroOrAllOfValidatorTest.java +++ b/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/ZeroOrAllOfValidatorTest.java @@ -14,20 +14,20 @@ class ZeroOrAllOfValidatorTest { @Test void okZero() { - assertThat(new ZeroOrAllOfValidator(Arrays.asList(Input.body("cbeNumber", null), Input.body("sector", null))) + assertThat(new ZeroOrAllOfValidator(Arrays.asList(Input.body("/cbeNumber", null), Input.body("/sector", null))) .validate()).isEmpty(); } @Test void okAll() { - assertThat(new ZeroOrAllOfValidator(Arrays.asList(Input.body("cbeNumber", "25"), Input.body("sector", "25"))) + assertThat(new ZeroOrAllOfValidator(Arrays.asList(Input.body("/cbeNumber", "25"), Input.body("/sector", "25"))) .validate()).isEmpty(); } @Test void nok() { - List> items = Arrays.asList(Input.body("cbeNumber", "0694965804"), - Input.body("sector", null)); + List> items = Arrays.asList(Input.body("/cbeNumber", "0694965804"), + Input.body("/sector", null)); assertThat(new ZeroOrAllOfValidator(items).validate()).contains( InputValidationIssues.zeroOrAllOfExpected(items)); } diff --git a/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/ZeroOrExactlyOneOfValidatorTest.java b/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/ZeroOrExactlyOneOfValidatorTest.java index 7e432959..f987e986 100644 --- a/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/ZeroOrExactlyOneOfValidatorTest.java +++ b/belgif-rest-problem-validator/src/test/java/io/github/belgif/rest/problem/validation/ZeroOrExactlyOneOfValidatorTest.java @@ -15,21 +15,21 @@ class ZeroOrExactlyOneOfValidatorTest { @Test void okZero() { assertThat(new ZeroOrExactlyOneOfValidator( - Arrays.asList(Input.body("cbeNumber", null), Input.body("sector", null))) + Arrays.asList(Input.body("/cbeNumber", null), Input.body("/sector", null))) .validate()).isEmpty(); } @Test void okOne() { assertThat(new ZeroOrExactlyOneOfValidator( - Arrays.asList(Input.body("cbeNumber", null), Input.body("sector", "25"))) + Arrays.asList(Input.body("/cbeNumber", null), Input.body("/sector", "25"))) .validate()).isEmpty(); } @Test void validateNOk() { - List> items = Arrays.asList(Input.body("cbeNumber", "0694965804"), - Input.body("sector", "25")); + List> items = Arrays.asList(Input.body("/cbeNumber", "0694965804"), + Input.body("/sector", "25")); assertThat(new ZeroOrExactlyOneOfValidator(items).validate()).contains( InputValidationIssues.zeroOrExactlyOneOfExpected(items)); } diff --git a/belgif-rest-problem/pom.xml b/belgif-rest-problem/pom.xml index 6ee94493..6fa34c3b 100644 --- a/belgif-rest-problem/pom.xml +++ b/belgif-rest-problem/pom.xml @@ -59,6 +59,12 @@ --> true + + org.slf4j + slf4j-api + 2.0.18 + provided + org.junit.jupiter junit-jupiter diff --git a/belgif-rest-problem/src/main/java/io/github/belgif/rest/problem/api/Input.java b/belgif-rest-problem/src/main/java/io/github/belgif/rest/problem/api/Input.java index cf60474c..119e5fcb 100644 --- a/belgif-rest-problem/src/main/java/io/github/belgif/rest/problem/api/Input.java +++ b/belgif-rest-problem/src/main/java/io/github/belgif/rest/problem/api/Input.java @@ -33,7 +33,7 @@ public Input() { public Input(InEnum in, String name, V value) { this.in = in; - this.name = name; + this.name = InputValidationIssue.convertName(in, name); this.value = value; } @@ -43,6 +43,7 @@ public InEnum getIn() { public void setIn(InEnum in) { this.in = in; + setName(name); } public String getName() { @@ -50,7 +51,7 @@ public String getName() { } public void setName(String name) { - this.name = name; + this.name = InputValidationIssue.convertName(in, name); } public V getValue() { diff --git a/belgif-rest-problem/src/main/java/io/github/belgif/rest/problem/api/InputValidationIssue.java b/belgif-rest-problem/src/main/java/io/github/belgif/rest/problem/api/InputValidationIssue.java index 3ca02753..7e6b8be9 100644 --- a/belgif-rest-problem/src/main/java/io/github/belgif/rest/problem/api/InputValidationIssue.java +++ b/belgif-rest-problem/src/main/java/io/github/belgif/rest/problem/api/InputValidationIssue.java @@ -13,6 +13,9 @@ import java.util.Objects; import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; import com.fasterxml.jackson.annotation.JsonInclude; @@ -41,12 +44,17 @@ public class InputValidationIssue { public static final Comparator BY_NAME = Comparator.comparing(InputValidationIssue::getName); + private static final Logger LOGGER = LoggerFactory.getLogger(InputValidationIssue.class); + private static final String INPUTS_AND_IN_NAME_VALUE_ARE_MUTUALLY_EXCLUSIVE = "inputs[] and in/name/value are mutually exclusive"; private static final String INPUTS_SETTER_ONE_ITEM = "inputs[] can not be set with a single item, use in(in, name, value) instead"; + // e.g: /, field, /field, /field/0, /field/0/nested + private static final String JSON_POINTER_BASIC_REGEX = "\\/+[a-zA-Z0-9-]*+(\\/[a-zA-Z0-9-]++)*+"; + private URI type; private URI href; private String title; @@ -73,13 +81,13 @@ public InputValidationIssue(URI type, URI href, String title) { public InputValidationIssue(InEnum in, String name, Object value) { this.in = in; - this.name = name; + this.name = convertName(in, name); this.value = value; } public InputValidationIssue(InEnum in, String name) { this.in = in; - this.name = name; + this.name = convertName(in, name); } public URI getType() { @@ -121,6 +129,7 @@ public InEnum getIn() { public void setIn(InEnum in) { verifyNoInputs(in); this.in = in; + this.name = convertName(in, name); } public String getName() { @@ -129,7 +138,7 @@ public String getName() { public void setName(String name) { verifyNoInputs(name); - this.name = name; + this.name = convertName(in, name); } public Object getValue() { @@ -450,4 +459,68 @@ public String toString() { '}'; } + private static boolean nameMatchesJsonPointerFormat(String name) { + return name == null || (name.matches(JSON_POINTER_BASIC_REGEX) + && !name.matches(".*\\/\\d+\\/\\d+\\/*+") // not two indexes following each other (e.g: person/1/2) + && !name.matches("\\/+\\d++(\\/[a-zA-Z0-9-.]++)*+")); // not starting with an index (e.g: /1/person) + } + + /** + * + * @param in the issue place in the query + * @param nameJsonPath the name in JsonPath syntax + * @return the name converted to JsonPointer syntax + */ + public static String transformName(InEnum in, String nameJsonPath) { + + if (nameJsonPath == null || nameJsonPath.trim().isEmpty()) { + return null; + } else if (!ProblemConfig.isJsonPointerEnabled() || in != InEnum.BODY) { + return nameJsonPath; + } else { + // replace all indexes "[X]" by "/X" and replace all "." by "/" + String convertedName = replaceSquareBrackets(nameJsonPath).replace(".", "/"); + return convertedName.charAt(0) != '/' ? "/" + convertedName : convertedName; + } + } + + /** + * + * @param in the issue place in the query + * @param name the name in JsonPath syntax + * @return the name converted (if necessary) to JsonPointer syntax + */ + protected static String convertName(InEnum in, String name) { + if (in == InEnum.BODY && ProblemConfig.isJsonPointerEnabled() && !nameMatchesJsonPointerFormat(name)) { + LOGGER.warn( + "Your application does not use the JsonPointer syntax for issue in the body although it is " + + "enabled [in: %s, name: %s]. Auto-conversion will be applied. ", + in, name); + return transformName(in, name); + } + + return name; + } + + public static String getNameFromProperties(InEnum in, List propertiesName) { + + if (in != InEnum.BODY && propertiesName != null && propertiesName.size() > 1) { + throw new IllegalArgumentException( + "This method should only be used with several properties for issues located in the body"); + } + + if (propertiesName == null || propertiesName.isEmpty()) { + return null; + } + + String name = ProblemConfig.isJsonPointerEnabled() && in == InEnum.BODY ? propertiesName.stream() + .map(InputValidationIssue::replaceSquareBrackets).collect(Collectors.joining("/")) + : String.join(".", propertiesName); + return in == InEnum.BODY && ProblemConfig.isJsonPointerEnabled() ? "/" + name : name; + } + + private static String replaceSquareBrackets(String propertyName) { + // replace all indexes "[X]" by "/X" + return propertyName.replaceAll("\\[(\\d++)\\]", "/$1"); + } } diff --git a/belgif-rest-problem/src/main/java/io/github/belgif/rest/problem/api/InputValidationIssues.java b/belgif-rest-problem/src/main/java/io/github/belgif/rest/problem/api/InputValidationIssues.java index b3d6a3c5..9ae7c8a6 100644 --- a/belgif-rest-problem/src/main/java/io/github/belgif/rest/problem/api/InputValidationIssues.java +++ b/belgif-rest-problem/src/main/java/io/github/belgif/rest/problem/api/InputValidationIssues.java @@ -139,8 +139,10 @@ public static InputValidationIssue referencedResourceNotFound(InEnum in, String * @param The type of the reference */ public static InputValidationIssue referencedResourceNotFound(InEnum in, String name, T value, List source) { - String nameWithIndex = name + "[" + source.indexOf(value) + "]"; - return referencedResourceNotFound(in, nameWithIndex, value); + String indexFormat = ProblemConfig.isJsonPointerEnabled() && in == InEnum.BODY ? ("/" + source.indexOf(value)) + : ("[" + source.indexOf(value) + "]"); + String nameWithIndex = name + indexFormat; + return referencedResourceNotFound(in, InputValidationIssue.transformName(in, nameWithIndex), value); } public static InputValidationIssue rejectedInput(InEnum in, String name, Object value) { diff --git a/belgif-rest-problem/src/main/java/io/github/belgif/rest/problem/config/ProblemConfig.java b/belgif-rest-problem/src/main/java/io/github/belgif/rest/problem/config/ProblemConfig.java index 8ca23093..1e7b5ebd 100644 --- a/belgif-rest-problem/src/main/java/io/github/belgif/rest/problem/config/ProblemConfig.java +++ b/belgif-rest-problem/src/main/java/io/github/belgif/rest/problem/config/ProblemConfig.java @@ -17,6 +17,9 @@ public class ProblemConfig { public static final String PROPERTY_EXT_INPUTS_ARRAY_ENABLED = "io.github.belgif.rest.problem.ext.inputs-array-enabled"; + public static final String PROPERTY_JSON_POINTER_ENABLED = + "io.github.belgif.rest.problem.json-pointer-enabled"; + private static final boolean DEFAULT_I18N_ENABLED = true; private static final boolean DEFAULT_STACK_TRACE_ENABLED = false; @@ -24,6 +27,7 @@ public class ProblemConfig { private static final boolean DEFAULT_EXT_ISSUE_TYPES_ENABLED = false; private static final boolean DEFAULT_EXT_INPUTS_ARRAY_ENABLED = false; + private static final boolean DEFAULT_JSON_POINTER_ENABLED = true; private static boolean i18nEnabled = DEFAULT_I18N_ENABLED; @@ -33,6 +37,8 @@ public class ProblemConfig { private static boolean extInputsArrayEnabled = DEFAULT_EXT_INPUTS_ARRAY_ENABLED; + private static boolean jsonPointerEnabled = DEFAULT_JSON_POINTER_ENABLED; + private static final ThreadLocal LOCAL_EXT_ISSUE_TYPES_ENABLED = new InheritableThreadLocal() { @Override protected Boolean initialValue() { @@ -96,6 +102,14 @@ public static void setLocalExtInputsArrayEnabled(boolean extInputsArrayEnabled) LOCAL_EXT_INPUTS_ARRAY_ENABLED.set(extInputsArrayEnabled); } + public static boolean isJsonPointerEnabled() { + return jsonPointerEnabled; + } + + public static void setJsonPointerEnabled(boolean jsonPointerEnabled) { + ProblemConfig.jsonPointerEnabled = jsonPointerEnabled; + } + public static void clearLocal() { LOCAL_EXT_ISSUE_TYPES_ENABLED.remove(); LOCAL_EXT_INPUTS_ARRAY_ENABLED.remove(); @@ -106,6 +120,7 @@ public static void reset() { stackTraceEnabled = DEFAULT_STACK_TRACE_ENABLED; extIssueTypesEnabled = DEFAULT_EXT_ISSUE_TYPES_ENABLED; extInputsArrayEnabled = DEFAULT_EXT_INPUTS_ARRAY_ENABLED; + jsonPointerEnabled = DEFAULT_JSON_POINTER_ENABLED; } } diff --git a/belgif-rest-problem/src/main/java/io/github/belgif/rest/problem/internal/Jackson2Util.java b/belgif-rest-problem/src/main/java/io/github/belgif/rest/problem/internal/Jackson2Util.java index 4ee30822..02e97557 100644 --- a/belgif-rest-problem/src/main/java/io/github/belgif/rest/problem/internal/Jackson2Util.java +++ b/belgif-rest-problem/src/main/java/io/github/belgif/rest/problem/internal/Jackson2Util.java @@ -2,6 +2,7 @@ import static io.github.belgif.rest.problem.api.InputValidationIssues.*; +import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -12,6 +13,7 @@ import io.github.belgif.rest.problem.BadRequestProblem; import io.github.belgif.rest.problem.api.InEnum; +import io.github.belgif.rest.problem.api.InputValidationIssue; import io.github.belgif.rest.problem.api.InputValidationIssues; /** @@ -59,21 +61,22 @@ public static BadRequestProblem toBadRequestProblem(JsonMappingException e) { } private static String getName(List path) { + if (path.isEmpty()) { return null; } - StringBuilder builder = new StringBuilder(); + List properties = new ArrayList<>(); + for (Reference reference : path) { if (reference.getFrom() instanceof List) { - builder.append("[").append(reference.getIndex()).append("]"); + // append the index to the property name + properties.set(properties.size() - 1, + properties.get(properties.size() - 1) + "[" + reference.getIndex() + "]"); } else { - if (builder.length() > 0) { - builder.append("."); - } - builder.append(reference.getFieldName()); + properties.add(reference.getFieldName()); } } - return builder.toString(); + return InputValidationIssue.getNameFromProperties(InEnum.BODY, properties); } @SuppressWarnings("java:S1872") diff --git a/belgif-rest-problem/src/main/java/io/github/belgif/rest/problem/internal/Jackson3Util.java b/belgif-rest-problem/src/main/java/io/github/belgif/rest/problem/internal/Jackson3Util.java index b7942490..09f7655a 100644 --- a/belgif-rest-problem/src/main/java/io/github/belgif/rest/problem/internal/Jackson3Util.java +++ b/belgif-rest-problem/src/main/java/io/github/belgif/rest/problem/internal/Jackson3Util.java @@ -2,10 +2,12 @@ import static io.github.belgif.rest.problem.api.InputValidationIssues.*; +import java.util.ArrayList; import java.util.List; import io.github.belgif.rest.problem.BadRequestProblem; import io.github.belgif.rest.problem.api.InEnum; +import io.github.belgif.rest.problem.api.InputValidationIssue; import io.github.belgif.rest.problem.api.InputValidationIssues; import tools.jackson.core.JacksonException.Reference; import tools.jackson.core.exc.StreamReadException; @@ -43,21 +45,22 @@ public static BadRequestProblem toBadRequestProblem(DatabindException e) { } private static String getName(List path) { + if (path.isEmpty()) { return null; } - StringBuilder name = new StringBuilder(); + + List properties = new ArrayList<>(); + for (Reference reference : path) { if (reference.from() instanceof List) { - name.append("[").append(reference.getIndex()).append("]"); + // append the index to the property name + properties.set(properties.size() - 1, + properties.get(properties.size() - 1) + "[" + reference.getIndex() + "]"); } else { - if (name.length() > 0) { - name.append("."); - } - name.append(reference.getPropertyName()); + properties.add(reference.getPropertyName()); } } - return name.toString(); + return InputValidationIssue.getNameFromProperties(InEnum.BODY, properties); } - } diff --git a/belgif-rest-problem/src/test/java/io/github/belgif/rest/problem/api/InputTest.java b/belgif-rest-problem/src/test/java/io/github/belgif/rest/problem/api/InputTest.java index da9dd644..d58dd986 100644 --- a/belgif-rest-problem/src/test/java/io/github/belgif/rest/problem/api/InputTest.java +++ b/belgif-rest-problem/src/test/java/io/github/belgif/rest/problem/api/InputTest.java @@ -2,10 +2,18 @@ import static org.assertj.core.api.Assertions.*; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import io.github.belgif.rest.problem.config.ProblemConfig; + class InputTest { + @BeforeEach + void resetProblemConfig() { + ProblemConfig.reset(); + } + @Test void construct() { Input input = new Input<>(); @@ -21,10 +29,10 @@ void construct() { @Test void body() { - Input bodyInput = Input.body("bodyName", "bodyValue"); + Input bodyInput = Input.body("/bodyName", "bodyValue"); assertThat(bodyInput.getIn()).isEqualTo(InEnum.BODY); - assertThat(bodyInput.getName()).isEqualTo("bodyName"); + assertThat(bodyInput.getName()).isEqualTo("/bodyName"); assertThat(bodyInput.getValue()).isEqualTo("bodyValue"); } @@ -57,9 +65,9 @@ void header() { @Test void equalsHashCodeToString() { - Input input = new Input<>(InEnum.BODY, "name", "value"); - Input equal = new Input<>(InEnum.BODY, "name", "value"); - Input other = new Input<>(InEnum.BODY, "anotherName", "anotherValue"); + Input input = new Input<>(InEnum.BODY, "/name", "value"); + Input equal = new Input<>(InEnum.BODY, "/name", "value"); + Input other = new Input<>(InEnum.BODY, "/anotherName", "anotherValue"); assertThat(input).isEqualTo(input); assertThat(input).hasSameHashCodeAs(input); diff --git a/belgif-rest-problem/src/test/java/io/github/belgif/rest/problem/api/InputValidationIssueTest.java b/belgif-rest-problem/src/test/java/io/github/belgif/rest/problem/api/InputValidationIssueTest.java index 96d1e10c..d54e8d4e 100644 --- a/belgif-rest-problem/src/test/java/io/github/belgif/rest/problem/api/InputValidationIssueTest.java +++ b/belgif-rest-problem/src/test/java/io/github/belgif/rest/problem/api/InputValidationIssueTest.java @@ -6,12 +6,16 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.assertj.core.api.InstanceOfAssertFactories; import org.assertj.core.api.ThrowableAssert; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; import io.github.belgif.rest.problem.config.ProblemConfig; import io.github.belgif.rest.problem.i18n.Context; @@ -317,10 +321,252 @@ void equalsHashCodeToString() { assertThat(issue).isNotEqualTo("other type"); } + @ParameterizedTest + @EnumSource(InEnum.class) + void transformNameInBody(InEnum in) { + assertThat(InputValidationIssue.transformName(in, null)).isNull(); + assertThat(InputValidationIssue.transformName(in, "")).isNull(); + assertThat(InputValidationIssue.transformName(in, " ")).isNull(); + assertThat(InputValidationIssue.transformName(in, "field")).isEqualTo(in == InEnum.BODY ? "/field" : "field"); + assertThat(InputValidationIssue.transformName(in, "field[0]")) + .isEqualTo(in == InEnum.BODY ? "/field/0" : "field[0]"); + assertThat(InputValidationIssue.transformName(in, "field[0].nested")) + .isEqualTo(in == InEnum.BODY ? "/field/0/nested" : "field[0].nested"); + assertThat(InputValidationIssue.transformName(in, "field/0")) + .isEqualTo(in == InEnum.BODY ? "/field/0" : "field/0"); + assertThat(InputValidationIssue.transformName(in, "field/0/nested")) + .isEqualTo(in == InEnum.BODY ? "/field/0/nested" : "field/0/nested"); + assertThat(InputValidationIssue.transformName(in, "/field/0")).isEqualTo("/field/0"); + assertThat(InputValidationIssue.transformName(in, "/field/0/nested")).isEqualTo("/field/0/nested"); + assertThat(InputValidationIssue.transformName(in, "/field")).isEqualTo("/field"); + } + + @ParameterizedTest + @EnumSource(InEnum.class) + void transformNameInBodyJsonPointerDisabled(InEnum in) { + ProblemConfig.setJsonPointerEnabled(false); + + assertThat(InputValidationIssue.transformName(in, null)).isNull(); + assertThat(InputValidationIssue.transformName(in, "")).isNull(); + assertThat(InputValidationIssue.transformName(in, " ")).isNull(); + assertThat(InputValidationIssue.transformName(in, "field")).isEqualTo("field"); + assertThat(InputValidationIssue.transformName(in, "field[0]")).isEqualTo("field[0]"); + assertThat(InputValidationIssue.transformName(in, "field[0].nested")).isEqualTo("field[0].nested"); + assertThat(InputValidationIssue.transformName(in, "field/0")).isEqualTo("field/0"); + assertThat(InputValidationIssue.transformName(in, "field/0/nested")).isEqualTo("field/0/nested"); + assertThat(InputValidationIssue.transformName(in, "/field/0")).isEqualTo("/field/0"); + assertThat(InputValidationIssue.transformName(in, "/field/0/nested")).isEqualTo("/field/0/nested"); + assertThat(InputValidationIssue.transformName(in, "/field")).isEqualTo("/field"); + } + + @ParameterizedTest + @EnumSource(InEnum.class) + void getNameFromProperties(InEnum in) { + List properties = null; + assertThat(InputValidationIssue.getNameFromProperties(in, properties)).isNull(); + + properties = new ArrayList<>(); + assertThat(InputValidationIssue.getNameFromProperties(in, properties)).isNull(); + + properties.add("field"); + assertThat(InputValidationIssue.getNameFromProperties(in, properties)) + .isEqualTo(in == InEnum.BODY ? "/field" : "field"); + + properties.set(0, "field[0]"); + assertThat(InputValidationIssue.getNameFromProperties(in, properties)) + .isEqualTo(in == InEnum.BODY ? "/field/0" : "field[0]"); + + if (in == InEnum.BODY) { + properties.add("nested"); + assertThat(InputValidationIssue.getNameFromProperties(in, properties)).isEqualTo("/field/0/nested"); + + properties.add("nestedAgain[1]"); + assertThat(InputValidationIssue.getNameFromProperties(in, properties)) + .isEqualTo("/field/0/nested/nestedAgain/1"); + } + } + + @ParameterizedTest + @EnumSource(InEnum.class) + void getNameFromPropertiesWithJsonPointerDisabled(InEnum in) { + + ProblemConfig.setJsonPointerEnabled(false); + + List properties = null; + assertThat(InputValidationIssue.getNameFromProperties(in, properties)).isNull(); + + properties = new ArrayList<>(); + assertThat(InputValidationIssue.getNameFromProperties(in, properties)).isNull(); + + properties.add("field"); + assertThat(InputValidationIssue.getNameFromProperties(in, properties)).isEqualTo("field"); + + properties.set(0, "field[0]"); + assertThat(InputValidationIssue.getNameFromProperties(in, properties)).isEqualTo("field[0]"); + + if (in == InEnum.BODY) { + properties.add("nested"); + assertThat(InputValidationIssue.getNameFromProperties(in, properties)).isEqualTo("field[0].nested"); + assertThat(InputValidationIssue.getNameFromProperties(in, properties)).isEqualTo("field[0].nested"); + + properties.add("nestedAgain[1]"); + assertThat(InputValidationIssue.getNameFromProperties(in, properties)) + .isEqualTo("field[0].nested.nestedAgain[1]"); + } + } + + @ParameterizedTest + @EnumSource(value = InEnum.class, names = { "BODY" }, mode = EnumSource.Mode.EXCLUDE) + void getNameFromPropertiesIllegalArgument() { + assertThatIllegalArgumentException() + .isThrownBy(() -> InputValidationIssue.getNameFromProperties(InEnum.QUERY, + Arrays.asList("field", "nested"))) + .withMessageContaining("located in the body"); + assertThatIllegalArgumentException() + .isThrownBy( + () -> InputValidationIssue.getNameFromProperties(InEnum.PATH, Arrays.asList("field", "nested"))) + .withMessageContaining("located in the body"); + assertThatIllegalArgumentException() + .isThrownBy(() -> InputValidationIssue.getNameFromProperties(InEnum.HEADER, + Arrays.asList("field", "nested"))) + .withMessageContaining("located in the body"); + } + + @ParameterizedTest + @EnumSource(InEnum.class) + void nameMatchingJsonPointerFormatInConstructor(InEnum in) { + List names = Arrays.asList("/field", "/field/0", "/field/0/nested/2/nestedAgain"); + + for (String name : names) { + InputValidationIssue input = new InputValidationIssue(in, name); + assertThat(input.getName()).isEqualTo(name); + input = new InputValidationIssue(in, name, "value"); + assertThat(input.getName()).isEqualTo(name); + } + } + + @ParameterizedTest + @EnumSource(InEnum.class) + void nameNotMatchingJsonPointerFormatInConstructorAutoConversion(InEnum in) { + Map names = new HashMap<>(); + names.put("field", "/field"); + names.put("", null); + names.put("field[0]", "/field/0"); + names.put("field[0].nested[2].nestedAgain", "/field/0/nested/2/nestedAgain"); + names.put("field/0", "/field/0"); + names.put("field/0/nested/2/nestedAgain", "/field/0/nested/2/nestedAgain"); + + for (Map.Entry name : names.entrySet()) { + InputValidationIssue input = new InputValidationIssue(in, name.getKey()); + assertThat(input.getName()).isEqualTo(in == InEnum.BODY ? name.getValue() : name.getKey()); + input = new InputValidationIssue(in, name.getKey(), "value"); + assertThat(input.getName()).isEqualTo(in == InEnum.BODY ? name.getValue() : name.getKey()); + } + } + + @ParameterizedTest + @EnumSource(InEnum.class) + void nameNotConvertedInConstructorWithJsonPointerDisabled(InEnum in) { + ProblemConfig.setJsonPointerEnabled(false); + + List names = Arrays.asList("field", "/field", "", null, "field[0]", "/field/0", "field/0", + "field[0].nested[2].nestedAgain", "field/0/nested/2/nestedAgain", "/field/0/nested/2/nestedAgain"); + + for (String name : names) { + InputValidationIssue input = new InputValidationIssue(in, name); + assertThat(input.getName()).isEqualTo(name); + input = new InputValidationIssue(in, name, "value"); + assertThat(input.getName()).isEqualTo(name); + } + } + + @ParameterizedTest + @EnumSource(InEnum.class) + void setName(InEnum in) { + Map names = new HashMap<>(); + names.put("field", "/field"); + names.put("/field", "/field"); + names.put("", null); + names.put("field[0]", "/field/0"); + names.put("field[0].nested[2].nestedAgain", "/field/0/nested/2/nestedAgain"); + names.put("field/0", "/field/0"); + names.put("/field/0", "/field/0"); + names.put("field/0/nested/2/nestedAgain", "/field/0/nested/2/nestedAgain"); + names.put("/field/0/nested/2/nestedAgain", "/field/0/nested/2/nestedAgain"); + + for (Map.Entry name : names.entrySet()) { + InputValidationIssue input = new InputValidationIssue(null, name.getKey(), null); + input.setName(name.getKey()); + assertThat(input.getName()).isEqualTo(name.getKey()); + input = new InputValidationIssue(in, null, null); + input.setName(name.getKey()); + assertThat(input.getName()).isEqualTo(in == InEnum.BODY ? name.getValue() : name.getKey()); + } + } + + @ParameterizedTest + @EnumSource(InEnum.class) + void setNameJsonPointerDisabled(InEnum in) { + ProblemConfig.setJsonPointerEnabled(false); + + List names = Arrays.asList("field", "/field", "", "field[0]", "/field/0", + "field[0].nested[2].nestedAgain", "/field/0/nested/2/nestedAgain", "field/0", + "field/0/nested/2/nestedAgain", "/field/0/nested/2/nestedAgain", "/field/0/nested/2/nestedAgain", + "/field/0/nested/2/nestedAgain"); + + for (String name : names) { + InputValidationIssue input = new InputValidationIssue(null, name, null); + input.setName(name); + assertThat(input.getName()).isEqualTo(name); + input = new InputValidationIssue(in, null, null); + input.setName(name); + assertThat(input.getName()).isEqualTo(name); + } + } + + @ParameterizedTest + @EnumSource(InEnum.class) + void setInput(InEnum in) { + Map names = new HashMap<>(); + names.put("field", "/field"); + names.put("/field", "/field"); + names.put("", null); + names.put("field[0]", "/field/0"); + names.put("field[0].nested[2].nestedAgain", "/field/0/nested/2/nestedAgain"); + names.put("field/0", "/field/0"); + names.put("/field/0", "/field/0"); + names.put("field/0/nested/2/nestedAgain", "/field/0/nested/2/nestedAgain"); + names.put("/field/0/nested/2/nestedAgain", "/field/0/nested/2/nestedAgain"); + + for (Map.Entry name : names.entrySet()) { + InputValidationIssue input = new InputValidationIssue(null, name.getKey(), null); + assertThat(input.getName()).isEqualTo(name.getKey()); + input.setIn(in); + assertThat(input.getName()).isEqualTo(in == InEnum.BODY ? name.getValue() : name.getKey()); + } + } + + @ParameterizedTest + @EnumSource(InEnum.class) + void setInputJsonPointerDisabled(InEnum in) { + ProblemConfig.setJsonPointerEnabled(false); + + List names = Arrays.asList("field", "/field", "", "field[0]", "/field/0", + "field[0].nested[2].nestedAgain", "/field/0/nested/2/nestedAgain", "field/0", + "field/0/nested/2/nestedAgain", "/field/0/nested/2/nestedAgain", "/field/0/nested/2/nestedAgain", + "/field/0/nested/2/nestedAgain"); + + for (String name : names) { + InputValidationIssue input = new InputValidationIssue(null, name, null); + assertThat(input.getName()).isEqualTo(name); + input.setIn(in); + assertThat(input.getName()).isEqualTo(name); + } + } + private void assertMutuallyExclusiveException(ThrowableAssert.ThrowingCallable throwingCallable) { assertThatIllegalArgumentException() .isThrownBy(throwingCallable) .withMessageContaining(MUTUALLY_EXCLUSIVE_EXC); } - } diff --git a/belgif-rest-problem/src/test/java/io/github/belgif/rest/problem/api/InputValidationIssuesTest.java b/belgif-rest-problem/src/test/java/io/github/belgif/rest/problem/api/InputValidationIssuesTest.java index bb42165a..23348661 100644 --- a/belgif-rest-problem/src/test/java/io/github/belgif/rest/problem/api/InputValidationIssuesTest.java +++ b/belgif-rest-problem/src/test/java/io/github/belgif/rest/problem/api/InputValidationIssuesTest.java @@ -58,13 +58,13 @@ void resetProblemConfig() { @Test void schemaViolation() { InputValidationIssue issue = - InputValidationIssues.schemaViolation(InEnum.BODY, "test", "value", "detail"); + InputValidationIssues.schemaViolation(InEnum.BODY, "/test", "value", "detail"); assertThat(issue.getType()).hasToString("urn:problem-type:belgif:input-validation:schemaViolation"); assertThat(issue.getHref()) .hasToString("https://www.belgif.be/specification/rest/api-guide/issues/schemaViolation.html"); assertThat(issue.getTitle()).isEqualTo("Input value is invalid with respect to the schema"); assertThat(issue.getIn()).isEqualTo(InEnum.BODY); - assertThat(issue.getName()).isEqualTo("test"); + assertThat(issue.getName()).isEqualTo("/test"); assertThat(issue.getValue()).isEqualTo("value"); assertThat(issue.getDetail()).isEqualTo("detail"); assertThat(issue).extracting("inputs", "additionalProperties").allMatch(this::isEmpty); @@ -73,28 +73,28 @@ void schemaViolation() { @Test void unknownInput() { InputValidationIssue issue = - InputValidationIssues.unknownInput(InEnum.BODY, "oops", "value"); + InputValidationIssues.unknownInput(InEnum.BODY, "/oops", "value"); assertThat(issue.getType()).hasToString("urn:problem-type:belgif:input-validation:unknownInput"); assertThat(issue.getHref()) .hasToString("https://www.belgif.be/specification/rest/api-guide/issues/unknownInput.html"); assertThat(issue.getTitle()).isEqualTo("Unknown input"); assertThat(issue.getIn()).isEqualTo(InEnum.BODY); - assertThat(issue.getName()).isEqualTo("oops"); + assertThat(issue.getName()).isEqualTo("/oops"); assertThat(issue.getValue()).isEqualTo("value"); - assertThat(issue.getDetail()).isEqualTo("Input oops is unknown"); + assertThat(issue.getDetail()).isEqualTo("Input /oops is unknown"); assertThat(issue).extracting("inputs", "additionalProperties").allMatch(this::isEmpty); } @Test void invalidInput() { InputValidationIssue issue = - InputValidationIssues.invalidInput(InEnum.BODY, "oops", "value", "detail"); + InputValidationIssues.invalidInput(InEnum.BODY, "/oops", "value", "detail"); assertThat(issue.getType()).hasToString("urn:problem-type:belgif:input-validation:invalidInput"); assertThat(issue.getHref()) .hasToString("https://www.belgif.be/specification/rest/api-guide/issues/invalidInput.html"); assertThat(issue.getTitle()).isEqualTo("Invalid input"); assertThat(issue.getIn()).isEqualTo(InEnum.BODY); - assertThat(issue.getName()).isEqualTo("oops"); + assertThat(issue.getName()).isEqualTo("/oops"); assertThat(issue.getValue()).isEqualTo("value"); assertThat(issue.getDetail()).isEqualTo("detail"); assertThat(issue).extracting("inputs", "additionalProperties").allMatch(this::isEmpty); @@ -104,7 +104,7 @@ void invalidInput() { @MethodSource("toggleExtIssueTypes") void invalidStructure(boolean extIssueTypes) { InputValidationIssue issue = - InputValidationIssues.invalidStructure(InEnum.BODY, "test", "value", "detail"); + InputValidationIssues.invalidStructure(InEnum.BODY, "/test", "value", "detail"); if (extIssueTypes) { assertThat(issue.getType()).hasToString("urn:problem-type:belgif-ext:input-validation:invalidStructure"); assertThat(issue.getHref()) @@ -117,7 +117,7 @@ void invalidStructure(boolean extIssueTypes) { assertThat(issue.getTitle()).isEqualTo("Invalid input"); } assertThat(issue.getIn()).isEqualTo(InEnum.BODY); - assertThat(issue.getName()).isEqualTo("test"); + assertThat(issue.getName()).isEqualTo("/test"); assertThat(issue.getValue()).isEqualTo("value"); assertThat(issue.getDetail()).isEqualTo("detail"); assertThat(issue).extracting("inputs", "additionalProperties").allMatch(this::isEmpty); @@ -127,7 +127,7 @@ void invalidStructure(boolean extIssueTypes) { @MethodSource("toggleExtIssueTypes") void outOfRangeMinMax(boolean extIssueTypes) { InputValidationIssue issue = - InputValidationIssues.outOfRange(InEnum.BODY, "test", 6, 1, 5); + InputValidationIssues.outOfRange(InEnum.BODY, "/test", 6, 1, 5); if (extIssueTypes) { assertThat(issue.getType()).hasToString("urn:problem-type:belgif-ext:input-validation:outOfRange"); assertThat(issue.getHref()) @@ -140,9 +140,9 @@ void outOfRangeMinMax(boolean extIssueTypes) { assertThat(issue.getTitle()).isEqualTo("Invalid input"); } assertThat(issue.getIn()).isEqualTo(InEnum.BODY); - assertThat(issue.getName()).isEqualTo("test"); + assertThat(issue.getName()).isEqualTo("/test"); assertThat(issue.getValue()).isEqualTo(6); - assertThat(issue.getDetail()).isEqualTo("Input value test = 6 is out of range [1, 5]"); + assertThat(issue.getDetail()).isEqualTo("Input value /test = 6 is out of range [1, 5]"); assertThat(issue.getAdditionalProperties()).containsOnly(entry("minimum", "1"), entry("maximum", "5")); assertThat(issue.getInputs()).isEmpty(); } @@ -151,7 +151,7 @@ void outOfRangeMinMax(boolean extIssueTypes) { @MethodSource("toggleExtIssueTypes") void outOfRangeMin(boolean extIssueTypes) { InputValidationIssue issue = - InputValidationIssues.outOfRange(InEnum.BODY, "test", 0, 1, null); + InputValidationIssues.outOfRange(InEnum.BODY, "/test", 0, 1, null); if (extIssueTypes) { assertThat(issue.getType()).hasToString("urn:problem-type:belgif-ext:input-validation:outOfRange"); assertThat(issue.getHref()) @@ -164,9 +164,9 @@ void outOfRangeMin(boolean extIssueTypes) { assertThat(issue.getTitle()).isEqualTo("Invalid input"); } assertThat(issue.getIn()).isEqualTo(InEnum.BODY); - assertThat(issue.getName()).isEqualTo("test"); + assertThat(issue.getName()).isEqualTo("/test"); assertThat(issue.getValue()).isEqualTo(0); - assertThat(issue.getDetail()).isEqualTo("Input value test = 0 should be at least 1"); + assertThat(issue.getDetail()).isEqualTo("Input value /test = 0 should be at least 1"); assertThat(issue.getAdditionalProperties()).containsExactly(entry("minimum", "1")); assertThat(issue.getInputs()).isEmpty(); } @@ -175,7 +175,7 @@ void outOfRangeMin(boolean extIssueTypes) { @MethodSource("toggleExtIssueTypes") void outOfRangeMax(boolean extIssueTypes) { InputValidationIssue issue = - InputValidationIssues.outOfRange(InEnum.BODY, "test", 6, null, 5); + InputValidationIssues.outOfRange(InEnum.BODY, "/test", 6, null, 5); if (extIssueTypes) { assertThat(issue.getType()).hasToString("urn:problem-type:belgif-ext:input-validation:outOfRange"); assertThat(issue.getHref()) @@ -188,9 +188,9 @@ void outOfRangeMax(boolean extIssueTypes) { assertThat(issue.getTitle()).isEqualTo("Invalid input"); } assertThat(issue.getIn()).isEqualTo(InEnum.BODY); - assertThat(issue.getName()).isEqualTo("test"); + assertThat(issue.getName()).isEqualTo("/test"); assertThat(issue.getValue()).isEqualTo(6); - assertThat(issue.getDetail()).isEqualTo("Input value test = 6 should not exceed 5"); + assertThat(issue.getDetail()).isEqualTo("Input value /test = 6 should not exceed 5"); assertThat(issue.getAdditionalProperties()).containsExactly(entry("maximum", "5")); assertThat(issue.getInputs()).isEmpty(); } @@ -205,30 +205,31 @@ void outOfRangeMinAndMaxNull() { @Test void referencedResourceNotFound() { InputValidationIssue issue = - InputValidationIssues.referencedResourceNotFound(InEnum.BODY, "test", "value"); + InputValidationIssues.referencedResourceNotFound(InEnum.BODY, "/test", "value"); assertThat(issue.getType()).hasToString("urn:problem-type:belgif:input-validation:referencedResourceNotFound"); assertThat(issue.getHref()) .hasToString( "https://www.belgif.be/specification/rest/api-guide/issues/referencedResourceNotFound.html"); assertThat(issue.getTitle()).isEqualTo("Referenced resource not found"); assertThat(issue.getIn()).isEqualTo(InEnum.BODY); - assertThat(issue.getName()).isEqualTo("test"); + assertThat(issue.getName()).isEqualTo("/test"); assertThat(issue.getValue()).isEqualTo("value"); - assertThat(issue.getDetail()).isEqualTo("Referenced resource test = 'value' does not exist"); + assertThat(issue.getDetail()).isEqualTo("Referenced resource /test = 'value' does not exist"); assertThat(issue).extracting("inputs", "additionalProperties").allMatch(this::isEmpty); } @Test void referencedResourceNotFoundDifferentResourceAndParameterName() { InputValidationIssue issue = - InputValidationIssues.referencedResourceNotFound(InEnum.BODY, "partners", "organization", "0123456789"); + InputValidationIssues.referencedResourceNotFound(InEnum.BODY, "/partners", "organization", + "0123456789"); assertThat(issue.getType()).hasToString("urn:problem-type:belgif:input-validation:referencedResourceNotFound"); assertThat(issue.getHref()) .hasToString( "https://www.belgif.be/specification/rest/api-guide/issues/referencedResourceNotFound.html"); assertThat(issue.getTitle()).isEqualTo("Referenced resource not found"); assertThat(issue.getIn()).isEqualTo(InEnum.BODY); - assertThat(issue.getName()).isEqualTo("partners"); + assertThat(issue.getName()).isEqualTo("/partners"); assertThat(issue.getValue()).isEqualTo("0123456789"); assertThat(issue.getDetail()).isEqualTo("Referenced resource organization = '0123456789' does not exist"); assertThat(issue).extracting("inputs", "additionalProperties").allMatch(this::isEmpty); @@ -237,7 +238,7 @@ void referencedResourceNotFoundDifferentResourceAndParameterName() { @Test void referencedResourceFromCollectionParameterNotFound() { InputValidationIssue issue = - InputValidationIssues.referencedResourceNotFound(InEnum.BODY, "partners", 123, + InputValidationIssues.referencedResourceNotFound(InEnum.BODY, "/partners", 123, Arrays.asList(1, 123, 3)); assertThat(issue.getType()).hasToString("urn:problem-type:belgif:input-validation:referencedResourceNotFound"); assertThat(issue.getHref()) @@ -245,17 +246,23 @@ void referencedResourceFromCollectionParameterNotFound() { "https://www.belgif.be/specification/rest/api-guide/issues/referencedResourceNotFound.html"); assertThat(issue.getTitle()).isEqualTo("Referenced resource not found"); assertThat(issue.getIn()).isEqualTo(InEnum.BODY); - assertThat(issue.getName()).isEqualTo("partners[1]"); + assertThat(issue.getName()).isEqualTo("/partners/1"); assertThat(issue.getValue()).isEqualTo(123); - assertThat(issue.getDetail()).isEqualTo("Referenced resource partners[1] = '123' does not exist"); + assertThat(issue.getDetail()).isEqualTo("Referenced resource /partners/1 = '123' does not exist"); assertThat(issue).extracting("inputs", "additionalProperties").allMatch(this::isEmpty); + + ProblemConfig.setJsonPointerEnabled(false); + issue = InputValidationIssues.referencedResourceNotFound(InEnum.BODY, "partners", 123, + Arrays.asList(1, 123, 3)); + assertThat(issue.getName()).isEqualTo("partners[1]"); + assertThat(issue.getDetail()).isEqualTo("Referenced resource partners[1] = '123' does not exist"); } @ParameterizedTest @MethodSource("toggleExtIssueTypes") void rejectedInput(boolean extIssueTypes) { InputValidationIssue issue = - InputValidationIssues.rejectedInput(InEnum.BODY, "test", "value"); + InputValidationIssues.rejectedInput(InEnum.BODY, "/test", "value"); if (extIssueTypes) { assertThat(issue.getType()).hasToString("urn:problem-type:belgif-ext:input-validation:rejectedInput"); assertThat(issue.getHref()) @@ -268,9 +275,9 @@ void rejectedInput(boolean extIssueTypes) { assertThat(issue.getTitle()).isEqualTo("Invalid input"); } assertThat(issue.getIn()).isEqualTo(InEnum.BODY); - assertThat(issue.getName()).isEqualTo("test"); + assertThat(issue.getName()).isEqualTo("/test"); assertThat(issue.getValue()).isEqualTo("value"); - assertThat(issue.getDetail()).isEqualTo("Input test is not allowed in this context"); + assertThat(issue.getDetail()).isEqualTo("Input /test is not allowed in this context"); assertThat(issue).extracting("inputs", "additionalProperties").allMatch(this::isEmpty); } @@ -278,7 +285,7 @@ void rejectedInput(boolean extIssueTypes) { @MethodSource("toggleExtIssueTypes") void requiredInput(boolean extIssueTypes) { InputValidationIssue issue = - InputValidationIssues.requiredInput(InEnum.BODY, "test"); + InputValidationIssues.requiredInput(InEnum.BODY, "/test"); if (extIssueTypes) { assertThat(issue.getType()).hasToString("urn:problem-type:belgif-ext:input-validation:requiredInput"); assertThat(issue.getHref()) @@ -291,9 +298,9 @@ void requiredInput(boolean extIssueTypes) { assertThat(issue.getTitle()).isEqualTo("Invalid input"); } assertThat(issue.getIn()).isEqualTo(InEnum.BODY); - assertThat(issue.getName()).isEqualTo("test"); + assertThat(issue.getName()).isEqualTo("/test"); assertThat(issue.getValue()).isNull(); - assertThat(issue.getDetail()).isEqualTo("Input test is required in this context"); + assertThat(issue.getDetail()).isEqualTo("Input /test is required in this context"); assertThat(issue).extracting("inputs", "additionalProperties").allMatch(this::isEmpty); } @@ -330,11 +337,11 @@ void requiredInputsIfPresent(boolean extIssueTypes, boolean extInputsArray) { @Test void replacedSsin() { InputValidationIssue issue = - InputValidationIssues.replacedSsin(InEnum.BODY, "ssin", "00000000196", "00000000295"); + InputValidationIssues.replacedSsin(InEnum.BODY, "/ssin", "00000000196", "00000000295"); assertThat(issue.getType()).hasToString("urn:problem-type:cbss:input-validation:replacedSsin"); assertThat(issue.getTitle()).isEqualTo("SSIN has been replaced, use new SSIN"); assertThat(issue.getIn()).isEqualTo(InEnum.BODY); - assertThat(issue.getName()).isEqualTo("ssin"); + assertThat(issue.getName()).isEqualTo("/ssin"); assertThat(issue.getValue()).isEqualTo("00000000196"); assertThat(issue.getDetail()).isEqualTo("SSIN 00000000196 has been replaced by 00000000295"); assertThat(issue.getAdditionalProperties()).containsExactly(entry("replacedBy", "00000000295")); @@ -344,11 +351,11 @@ void replacedSsin() { @Test void replacedSsinInput() { InputValidationIssue issue = - InputValidationIssues.replacedSsin(Input.body("ssin", "00000000196"), "00000000295"); + InputValidationIssues.replacedSsin(Input.body("/ssin", "00000000196"), "00000000295"); assertThat(issue.getType()).hasToString("urn:problem-type:cbss:input-validation:replacedSsin"); assertThat(issue.getTitle()).isEqualTo("SSIN has been replaced, use new SSIN"); assertThat(issue.getIn()).isEqualTo(InEnum.BODY); - assertThat(issue.getName()).isEqualTo("ssin"); + assertThat(issue.getName()).isEqualTo("/ssin"); assertThat(issue.getValue()).isEqualTo("00000000196"); assertThat(issue.getDetail()).isEqualTo("SSIN 00000000196 has been replaced by 00000000295"); assertThat(issue.getAdditionalProperties()).containsExactly(entry("replacedBy", "00000000295")); @@ -358,12 +365,12 @@ void replacedSsinInput() { @Test void replacedSsinWithReplacedByHref() { InputValidationIssue issue = - InputValidationIssues.replacedSsin(InEnum.BODY, "ssin", "00000000196", "00000000295", + InputValidationIssues.replacedSsin(InEnum.BODY, "/ssin", "00000000196", "00000000295", URI.create("https://api.company.com/v1/employees?ssin=00000000295")); assertThat(issue.getType()).hasToString("urn:problem-type:cbss:input-validation:replacedSsin"); assertThat(issue.getTitle()).isEqualTo("SSIN has been replaced, use new SSIN"); assertThat(issue.getIn()).isEqualTo(InEnum.BODY); - assertThat(issue.getName()).isEqualTo("ssin"); + assertThat(issue.getName()).isEqualTo("/ssin"); assertThat(issue.getValue()).isEqualTo("00000000196"); assertThat(issue.getDetail()).isEqualTo("SSIN 00000000196 has been replaced by 00000000295"); assertThat(issue.getAdditionalProperties()).containsExactly( @@ -374,11 +381,11 @@ void replacedSsinWithReplacedByHref() { @Test void canceledSsin() { - InputValidationIssue issue = InputValidationIssues.canceledSsin(InEnum.BODY, "ssin", "00000000196"); + InputValidationIssue issue = InputValidationIssues.canceledSsin(InEnum.BODY, "/ssin", "00000000196"); assertThat(issue.getType()).hasToString("urn:problem-type:cbss:input-validation:canceledSsin"); assertThat(issue.getTitle()).isEqualTo("SSIN has been canceled"); assertThat(issue.getIn()).isEqualTo(InEnum.BODY); - assertThat(issue.getName()).isEqualTo("ssin"); + assertThat(issue.getName()).isEqualTo("/ssin"); assertThat(issue.getValue()).isEqualTo("00000000196"); assertThat(issue.getDetail()).isEqualTo("SSIN 00000000196 has been canceled"); assertThat(issue).extracting("href", "inputs", "additionalProperties").allMatch(this::isEmpty); @@ -386,11 +393,11 @@ void canceledSsin() { @Test void canceledSsinInput() { - InputValidationIssue issue = InputValidationIssues.canceledSsin(Input.body("ssin", "00000000196")); + InputValidationIssue issue = InputValidationIssues.canceledSsin(Input.body("/ssin", "00000000196")); assertThat(issue.getType()).hasToString("urn:problem-type:cbss:input-validation:canceledSsin"); assertThat(issue.getTitle()).isEqualTo("SSIN has been canceled"); assertThat(issue.getIn()).isEqualTo(InEnum.BODY); - assertThat(issue.getName()).isEqualTo("ssin"); + assertThat(issue.getName()).isEqualTo("/ssin"); assertThat(issue.getValue()).isEqualTo("00000000196"); assertThat(issue.getDetail()).isEqualTo("SSIN 00000000196 has been canceled"); assertThat(issue).extracting("href", "inputs", "additionalProperties").allMatch(this::isEmpty); @@ -399,7 +406,7 @@ void canceledSsinInput() { @ParameterizedTest @MethodSource("toggleExtIssueTypes") void invalidSsin(boolean extIssueTypes) { - InputValidationIssue issue = InputValidationIssues.invalidSsin(InEnum.BODY, "ssin", "00000000195"); + InputValidationIssue issue = InputValidationIssues.invalidSsin(InEnum.BODY, "/ssin", "00000000195"); if (extIssueTypes) { assertThat(issue.getType()).hasToString("urn:problem-type:belgif-ext:input-validation:invalidStructure"); assertThat(issue.getHref()) @@ -412,7 +419,7 @@ void invalidSsin(boolean extIssueTypes) { assertThat(issue.getTitle()).isEqualTo("Invalid input"); } assertThat(issue.getIn()).isEqualTo(InEnum.BODY); - assertThat(issue.getName()).isEqualTo("ssin"); + assertThat(issue.getName()).isEqualTo("/ssin"); assertThat(issue.getValue()).isEqualTo("00000000195"); assertThat(issue.getDetail()).isEqualTo("SSIN 00000000195 is invalid"); assertThat(issue).extracting("inputs", "additionalProperties").allMatch(this::isEmpty); @@ -421,7 +428,7 @@ void invalidSsin(boolean extIssueTypes) { @ParameterizedTest @MethodSource("toggleExtIssueTypes") void invalidSsinInput(boolean extIssueTypes) { - InputValidationIssue issue = InputValidationIssues.invalidSsin(Input.body("ssin", "00000000195")); + InputValidationIssue issue = InputValidationIssues.invalidSsin(Input.body("/ssin", "00000000195")); if (extIssueTypes) { assertThat(issue.getType()).hasToString("urn:problem-type:belgif-ext:input-validation:invalidStructure"); assertThat(issue.getHref()) @@ -434,7 +441,7 @@ void invalidSsinInput(boolean extIssueTypes) { assertThat(issue.getTitle()).isEqualTo("Invalid input"); } assertThat(issue.getIn()).isEqualTo(InEnum.BODY); - assertThat(issue.getName()).isEqualTo("ssin"); + assertThat(issue.getName()).isEqualTo("/ssin"); assertThat(issue.getValue()).isEqualTo("00000000195"); assertThat(issue.getDetail()).isEqualTo("SSIN 00000000195 is invalid"); assertThat(issue).extracting("inputs", "additionalProperties").allMatch(this::isEmpty); @@ -442,13 +449,13 @@ void invalidSsinInput(boolean extIssueTypes) { @Test void unknownSsin() { - InputValidationIssue issue = InputValidationIssues.unknownSsin(InEnum.BODY, "ssin", "00000000196"); + InputValidationIssue issue = InputValidationIssues.unknownSsin(InEnum.BODY, "/ssin", "00000000196"); assertThat(issue.getType()).hasToString("urn:problem-type:belgif:input-validation:referencedResourceNotFound"); assertThat(issue.getHref()).hasToString( "https://www.belgif.be/specification/rest/api-guide/issues/referencedResourceNotFound.html"); assertThat(issue.getTitle()).isEqualTo("Referenced resource not found"); assertThat(issue.getIn()).isEqualTo(InEnum.BODY); - assertThat(issue.getName()).isEqualTo("ssin"); + assertThat(issue.getName()).isEqualTo("/ssin"); assertThat(issue.getValue()).isEqualTo("00000000196"); assertThat(issue.getDetail()).isEqualTo("SSIN 00000000196 does not exist"); assertThat(issue).extracting("inputs", "additionalProperties").allMatch(this::isEmpty); @@ -456,13 +463,13 @@ void unknownSsin() { @Test void unknownSsinInput() { - InputValidationIssue issue = InputValidationIssues.unknownSsin(Input.body("ssin", "00000000196")); + InputValidationIssue issue = InputValidationIssues.unknownSsin(Input.body("/ssin", "00000000196")); assertThat(issue.getType()).hasToString("urn:problem-type:belgif:input-validation:referencedResourceNotFound"); assertThat(issue.getHref()).hasToString( "https://www.belgif.be/specification/rest/api-guide/issues/referencedResourceNotFound.html"); assertThat(issue.getTitle()).isEqualTo("Referenced resource not found"); assertThat(issue.getIn()).isEqualTo(InEnum.BODY); - assertThat(issue.getName()).isEqualTo("ssin"); + assertThat(issue.getName()).isEqualTo("/ssin"); assertThat(issue.getValue()).isEqualTo("00000000196"); assertThat(issue.getDetail()).isEqualTo("SSIN 00000000196 does not exist"); assertThat(issue).extracting("inputs", "additionalProperties").allMatch(this::isEmpty); @@ -472,7 +479,7 @@ void unknownSsinInput() { @MethodSource("toggleExtIssueTypes") void invalidPeriod(boolean extIssueTypes) { InputValidationIssue issue = - InputValidationIssues.invalidPeriod(InEnum.BODY, "period", "value"); + InputValidationIssues.invalidPeriod(InEnum.BODY, "/period", "value"); if (extIssueTypes) { assertThat(issue.getType()).hasToString("urn:problem-type:belgif-ext:input-validation:invalidPeriod"); assertThat(issue.getHref()) @@ -485,7 +492,7 @@ void invalidPeriod(boolean extIssueTypes) { assertThat(issue.getTitle()).isEqualTo("Invalid input"); } assertThat(issue.getIn()).isEqualTo(InEnum.BODY); - assertThat(issue.getName()).isEqualTo("period"); + assertThat(issue.getName()).isEqualTo("/period"); assertThat(issue.getValue()).isEqualTo("value"); assertThat(issue.getDetail()).isEqualTo("endDate should not precede startDate"); assertThat(issue).extracting("inputs", "additionalProperties").allMatch(this::isEmpty); @@ -551,7 +558,7 @@ void invalidPeriodOffsetDateTime(boolean extIssueTypes, boolean extInputsArray) @MethodSource("toggleExtIssueTypes") void invalidIncompleteDate(boolean extIssueTypes) { InputValidationIssue issue = - InputValidationIssues.invalidIncompleteDate(InEnum.BODY, "test", "2024-00-01"); + InputValidationIssues.invalidIncompleteDate(InEnum.BODY, "/test", "2024-00-01"); if (extIssueTypes) { assertThat(issue.getType()).hasToString("urn:problem-type:belgif-ext:input-validation:invalidStructure"); assertThat(issue.getHref()) @@ -564,7 +571,7 @@ void invalidIncompleteDate(boolean extIssueTypes) { assertThat(issue.getTitle()).isEqualTo("Invalid input"); } assertThat(issue.getIn()).isEqualTo(InEnum.BODY); - assertThat(issue.getName()).isEqualTo("test"); + assertThat(issue.getName()).isEqualTo("/test"); assertThat(issue.getValue()).isEqualTo("2024-00-01"); assertThat(issue.getDetail()).isEqualTo("Incomplete date 2024-00-01 is invalid"); assertThat(issue).extracting("inputs", "additionalProperties").allMatch(this::isEmpty); @@ -574,7 +581,7 @@ void invalidIncompleteDate(boolean extIssueTypes) { @MethodSource("toggleExtIssueTypes") void invalidYearMonth(boolean extIssueTypes) { InputValidationIssue issue = - InputValidationIssues.invalidYearMonth(InEnum.BODY, "test", "2024-13"); + InputValidationIssues.invalidYearMonth(InEnum.BODY, "/test", "2024-13"); if (extIssueTypes) { assertThat(issue.getType()).hasToString("urn:problem-type:belgif-ext:input-validation:invalidStructure"); assertThat(issue.getHref()) @@ -587,7 +594,7 @@ void invalidYearMonth(boolean extIssueTypes) { assertThat(issue.getTitle()).isEqualTo("Invalid input"); } assertThat(issue.getIn()).isEqualTo(InEnum.BODY); - assertThat(issue.getName()).isEqualTo("test"); + assertThat(issue.getName()).isEqualTo("/test"); assertThat(issue.getValue()).isEqualTo("2024-13"); assertThat(issue.getDetail()).isEqualTo("Year month 2024-13 is invalid"); assertThat(issue).extracting("inputs", "additionalProperties").allMatch(this::isEmpty); @@ -597,7 +604,7 @@ void invalidYearMonth(boolean extIssueTypes) { @MethodSource("toggleExtIssueTypes") void invalidEnterpriseNumber(boolean extIssueTypes) { InputValidationIssue issue = - InputValidationIssues.invalidEnterpriseNumber(InEnum.BODY, "test", "0000000001"); + InputValidationIssues.invalidEnterpriseNumber(InEnum.BODY, "/test", "0000000001"); if (extIssueTypes) { assertThat(issue.getType()).hasToString("urn:problem-type:belgif-ext:input-validation:invalidStructure"); assertThat(issue.getHref()) @@ -610,7 +617,7 @@ void invalidEnterpriseNumber(boolean extIssueTypes) { assertThat(issue.getTitle()).isEqualTo("Invalid input"); } assertThat(issue.getIn()).isEqualTo(InEnum.BODY); - assertThat(issue.getName()).isEqualTo("test"); + assertThat(issue.getName()).isEqualTo("/test"); assertThat(issue.getValue()).isEqualTo("0000000001"); assertThat(issue.getDetail()).isEqualTo("Enterprise number 0000000001 is invalid"); assertThat(issue).extracting("inputs", "additionalProperties").allMatch(this::isEmpty); @@ -620,7 +627,7 @@ void invalidEnterpriseNumber(boolean extIssueTypes) { @MethodSource("toggleExtIssueTypes") void invalidEstablishmentUnitNumber(boolean extIssueTypes) { InputValidationIssue issue = - InputValidationIssues.invalidEstablishmentUnitNumber(InEnum.BODY, "test", "0000000001"); + InputValidationIssues.invalidEstablishmentUnitNumber(InEnum.BODY, "/test", "0000000001"); if (extIssueTypes) { assertThat(issue.getType()).hasToString("urn:problem-type:belgif-ext:input-validation:invalidStructure"); assertThat(issue.getHref()) @@ -633,7 +640,7 @@ void invalidEstablishmentUnitNumber(boolean extIssueTypes) { assertThat(issue.getTitle()).isEqualTo("Invalid input"); } assertThat(issue.getIn()).isEqualTo(InEnum.BODY); - assertThat(issue.getName()).isEqualTo("test"); + assertThat(issue.getName()).isEqualTo("/test"); assertThat(issue.getValue()).isEqualTo("0000000001"); assertThat(issue.getDetail()).isEqualTo("Establishment unit number 0000000001 is invalid"); assertThat(issue).extracting("inputs", "additionalProperties").allMatch(this::isEmpty); @@ -782,9 +789,9 @@ void getHrefConcurrently() throws Exception { List> results = new ArrayList<>(threads); for (int i = 0; i < threads; i++) { results.add(executorService.submit(() -> { - assertThat(InputValidationIssues.schemaViolation(InEnum.BODY, "name", "value", "detail").getHref()) + assertThat(InputValidationIssues.schemaViolation(InEnum.BODY, "/name", "value", "detail").getHref()) .hasToString("https://www.belgif.be/specification/rest/api-guide/issues/schemaViolation.html"); - assertThat(InputValidationIssues.invalidInput(InEnum.BODY, "name", "value", "detail").getHref()) + assertThat(InputValidationIssues.invalidInput(InEnum.BODY, "/name", "value", "detail").getHref()) .hasToString("https://www.belgif.be/specification/rest/api-guide/issues/invalidInput.html"); return null; })); diff --git a/belgif-rest-problem/src/test/java/io/github/belgif/rest/problem/internal/Jackson2UtilTest.java b/belgif-rest-problem/src/test/java/io/github/belgif/rest/problem/internal/Jackson2UtilTest.java index 5bd62a86..d41cd950 100644 --- a/belgif-rest-problem/src/test/java/io/github/belgif/rest/problem/internal/Jackson2UtilTest.java +++ b/belgif-rest-problem/src/test/java/io/github/belgif/rest/problem/internal/Jackson2UtilTest.java @@ -4,6 +4,7 @@ import java.util.List; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import com.fasterxml.jackson.annotation.JsonCreator; @@ -18,11 +19,33 @@ import io.github.belgif.rest.problem.BadRequestProblem; import io.github.belgif.rest.problem.api.InEnum; import io.github.belgif.rest.problem.api.InputValidationIssue; +import io.github.belgif.rest.problem.config.ProblemConfig; class Jackson2UtilTest { + @BeforeEach + void resetProblemConfif() { + ProblemConfig.reset(); + } + @Test void mismatchedInput() { + assertThatExceptionOfType(MismatchedInputException.class).isThrownBy(() -> { + new ObjectMapper().readValue("{}", Model.class); + }).satisfies(e -> { + BadRequestProblem problem = Jackson2Util.toBadRequestProblem(e); + InputValidationIssue issue = problem.getIssues().get(0); + assertThat(issue.getType()).hasToString("urn:problem-type:belgif:input-validation:schemaViolation"); + assertThat(issue.getIn()).isEqualTo(InEnum.BODY); + assertThat(issue.getName()).isEqualTo("/id"); + assertThat(issue.getValue()).isNull(); + assertThat(issue.getDetail()).isEqualTo("must not be null"); + }); + } + + @Test + void mismatchedInputWithJsonPointerDisabled() { + ProblemConfig.setJsonPointerEnabled(false); assertThatExceptionOfType(MismatchedInputException.class).isThrownBy(() -> { new ObjectMapper().readValue("{}", Model.class); }).satisfies(e -> { @@ -45,7 +68,7 @@ void mismatchedInputType() { InputValidationIssue issue = problem.getIssues().get(0); assertThat(issue.getType()).hasToString("urn:problem-type:belgif:input-validation:schemaViolation"); assertThat(issue.getIn()).isEqualTo(InEnum.BODY); - assertThat(issue.getName()).isEqualTo("nbr"); + assertThat(issue.getName()).isEqualTo("/nbr"); assertThat(issue.getValue()).isEqualTo("twenty-two"); assertThat(issue.getDetail()).isEqualTo("not a valid `int` value"); }); @@ -60,7 +83,7 @@ void valueInstantiationException() { InputValidationIssue issue = problem.getIssues().get(0); assertThat(issue.getType()).hasToString("urn:problem-type:belgif:input-validation:schemaViolation"); assertThat(issue.getIn()).isEqualTo(InEnum.BODY); - assertThat(issue.getName()).isEqualTo("size"); + assertThat(issue.getName()).isEqualTo("/size"); assertThat(issue.getValue()).isNull(); assertThat(issue.getDetail()).isEqualTo("Unexpected value 'XXL'"); }); @@ -75,7 +98,7 @@ void invalidFormatException() { InputValidationIssue issue = problem.getIssues().get(0); assertThat(issue.getType()).hasToString("urn:problem-type:belgif:input-validation:schemaViolation"); assertThat(issue.getIn()).isEqualTo(InEnum.BODY); - assertThat(issue.getName()).isEqualTo("size2"); + assertThat(issue.getName()).isEqualTo("/size2"); assertThat(issue.getValue()).isEqualTo("XXL"); assertThat(issue.getDetail()).isEqualTo("not one of the values accepted for enumeration: [S, L, M]"); }); @@ -90,7 +113,7 @@ void jsonParseExceptionWrappedJsonMappingException() { InputValidationIssue issue = problem.getIssues().get(0); assertThat(issue.getType()).hasToString("urn:problem-type:belgif:input-validation:schemaViolation"); assertThat(issue.getIn()).isEqualTo(InEnum.BODY); - assertThat(issue.getName()).isEqualTo("model"); + assertThat(issue.getName()).isEqualTo("/model"); assertThat(issue.getValue()).isNull(); assertThat(issue.getDetail()).isEqualTo("JSON syntax error"); }); @@ -135,7 +158,7 @@ void mismatchedInputNested() { InputValidationIssue issue = problem.getIssues().get(0); assertThat(issue.getType()).hasToString("urn:problem-type:belgif:input-validation:schemaViolation"); assertThat(issue.getIn()).isEqualTo(InEnum.BODY); - assertThat(issue.getName()).isEqualTo("model.id"); + assertThat(issue.getName()).isEqualTo("/model/id"); assertThat(issue.getValue()).isNull(); assertThat(issue.getDetail()).isEqualTo("must not be null"); }); @@ -143,6 +166,22 @@ void mismatchedInputNested() { @Test void mismatchedInputNestedWithArray() { + assertThatExceptionOfType(MismatchedInputException.class).isThrownBy(() -> { + new ObjectMapper().readValue("{\"models\": [{}]}", NestedWithArray.class); + }).satisfies(e -> { + BadRequestProblem problem = Jackson2Util.toBadRequestProblem(e); + InputValidationIssue issue = problem.getIssues().get(0); + assertThat(issue.getType()).hasToString("urn:problem-type:belgif:input-validation:schemaViolation"); + assertThat(issue.getIn()).isEqualTo(InEnum.BODY); + assertThat(issue.getName()).isEqualTo("/models/0/id"); + assertThat(issue.getValue()).isNull(); + assertThat(issue.getDetail()).isEqualTo("must not be null"); + }); + } + + @Test + void mismatchedInputNestedWithArrayWithJsonPointerDisabled() { + ProblemConfig.setJsonPointerEnabled(false); assertThatExceptionOfType(MismatchedInputException.class).isThrownBy(() -> { new ObjectMapper().readValue("{\"models\": [{}]}", NestedWithArray.class); }).satisfies(e -> { @@ -165,7 +204,7 @@ void mismatchedInputFormatError() { InputValidationIssue issue = problem.getIssues().get(0); assertThat(issue.getType()).hasToString("urn:problem-type:belgif:input-validation:schemaViolation"); assertThat(issue.getIn()).isEqualTo(InEnum.BODY); - assertThat(issue.getName()).isEqualTo("id"); + assertThat(issue.getName()).isEqualTo("/id"); assertThat(issue.getValue()).isEqualTo("one two three"); assertThat(issue.getDetail()).isEqualTo( "not a valid `int` value"); diff --git a/belgif-rest-problem/src/test/java/io/github/belgif/rest/problem/internal/Jackson3UtilTest.java b/belgif-rest-problem/src/test/java/io/github/belgif/rest/problem/internal/Jackson3UtilTest.java index 09cbf729..f93f8c9d 100644 --- a/belgif-rest-problem/src/test/java/io/github/belgif/rest/problem/internal/Jackson3UtilTest.java +++ b/belgif-rest-problem/src/test/java/io/github/belgif/rest/problem/internal/Jackson3UtilTest.java @@ -4,6 +4,7 @@ import java.util.List; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import com.fasterxml.jackson.annotation.JsonCreator; @@ -12,6 +13,7 @@ import io.github.belgif.rest.problem.BadRequestProblem; import io.github.belgif.rest.problem.api.InEnum; import io.github.belgif.rest.problem.api.InputValidationIssue; +import io.github.belgif.rest.problem.config.ProblemConfig; import tools.jackson.core.exc.StreamReadException; import tools.jackson.databind.DatabindException; import tools.jackson.databind.ObjectMapper; @@ -22,8 +24,29 @@ class Jackson3UtilTest { + @BeforeEach + void resetProblemConfif() { + ProblemConfig.reset(); + } + @Test void mismatchedInput() { + assertThatExceptionOfType(MismatchedInputException.class).isThrownBy(() -> { + new JsonMapper().readValue("{}", Model.class); + }).satisfies(e -> { + BadRequestProblem problem = Jackson3Util.toBadRequestProblem(e); + InputValidationIssue issue = problem.getIssues().get(0); + assertThat(issue.getType()).hasToString("urn:problem-type:belgif:input-validation:schemaViolation"); + assertThat(issue.getIn()).isEqualTo(InEnum.BODY); + assertThat(issue.getName()).isEqualTo("/id"); + assertThat(issue.getValue()).isNull(); + assertThat(issue.getDetail()).isEqualTo("must not be null"); + }); + } + + @Test + void mismatchedInputWithJsonPointerDisabled() { + ProblemConfig.setJsonPointerEnabled(false); assertThatExceptionOfType(MismatchedInputException.class).isThrownBy(() -> { new JsonMapper().readValue("{}", Model.class); }).satisfies(e -> { @@ -46,7 +69,7 @@ void mismatchedInputType() { InputValidationIssue issue = problem.getIssues().get(0); assertThat(issue.getType()).hasToString("urn:problem-type:belgif:input-validation:schemaViolation"); assertThat(issue.getIn()).isEqualTo(InEnum.BODY); - assertThat(issue.getName()).isEqualTo("nbr"); + assertThat(issue.getName()).isEqualTo("/nbr"); assertThat(issue.getValue()).isEqualTo("twenty-two"); assertThat(issue.getDetail()).isEqualTo("not a valid `int` value"); }); @@ -61,7 +84,7 @@ void valueInstantiationException() { InputValidationIssue issue = problem.getIssues().get(0); assertThat(issue.getType()).hasToString("urn:problem-type:belgif:input-validation:schemaViolation"); assertThat(issue.getIn()).isEqualTo(InEnum.BODY); - assertThat(issue.getName()).isEqualTo("size"); + assertThat(issue.getName()).isEqualTo("/size"); assertThat(issue.getValue()).isNull(); assertThat(issue.getDetail()).isEqualTo("Unexpected value 'XXL'"); }); @@ -76,7 +99,7 @@ void invalidFormatException() { InputValidationIssue issue = problem.getIssues().get(0); assertThat(issue.getType()).hasToString("urn:problem-type:belgif:input-validation:schemaViolation"); assertThat(issue.getIn()).isEqualTo(InEnum.BODY); - assertThat(issue.getName()).isEqualTo("size2"); + assertThat(issue.getName()).isEqualTo("/size2"); assertThat(issue.getValue()).isEqualTo("XXL"); assertThat(issue.getDetail()).isEqualTo("not one of the values accepted for enumeration: [S, L, M]"); }); @@ -91,7 +114,7 @@ void streamReadException() { InputValidationIssue issue = problem.getIssues().get(0); assertThat(issue.getType()).hasToString("urn:problem-type:belgif:input-validation:schemaViolation"); assertThat(issue.getIn()).isEqualTo(InEnum.BODY); - assertThat(issue.getName()).isEqualTo("model"); + assertThat(issue.getName()).isEqualTo("/model"); assertThat(issue.getValue()).isNull(); assertThat(issue.getDetail()).isEqualTo("JSON syntax error"); }); @@ -136,7 +159,7 @@ void mismatchedInputNested() { InputValidationIssue issue = problem.getIssues().get(0); assertThat(issue.getType()).hasToString("urn:problem-type:belgif:input-validation:schemaViolation"); assertThat(issue.getIn()).isEqualTo(InEnum.BODY); - assertThat(issue.getName()).isEqualTo("model.id"); + assertThat(issue.getName()).isEqualTo("/model/id"); assertThat(issue.getValue()).isNull(); assertThat(issue.getDetail()).isEqualTo("must not be null"); }); @@ -144,6 +167,22 @@ void mismatchedInputNested() { @Test void DatabindExceptionWithArray() { + assertThatExceptionOfType(DatabindException.class).isThrownBy(() -> { + new ObjectMapper().readValue("{\"models\": [{}]}", NestedWithArray.class); + }).satisfies(e -> { + BadRequestProblem problem = Jackson3Util.toBadRequestProblem(e); + InputValidationIssue issue = problem.getIssues().get(0); + assertThat(issue.getType()).hasToString("urn:problem-type:belgif:input-validation:schemaViolation"); + assertThat(issue.getIn()).isEqualTo(InEnum.BODY); + assertThat(issue.getName()).isEqualTo("/models/0/id"); + assertThat(issue.getValue()).isNull(); + assertThat(issue.getDetail()).isEqualTo("must not be null"); + }); + } + + @Test + void DatabindExceptionWithArrayJsonPointerDisabled() { + ProblemConfig.setJsonPointerEnabled(false); assertThatExceptionOfType(DatabindException.class).isThrownBy(() -> { new ObjectMapper().readValue("{\"models\": [{}]}", NestedWithArray.class); }).satisfies(e -> { @@ -166,7 +205,7 @@ void mismatchedInputFormatError() { InputValidationIssue issue = problem.getIssues().get(0); assertThat(issue.getType()).hasToString("urn:problem-type:belgif:input-validation:schemaViolation"); assertThat(issue.getIn()).isEqualTo(InEnum.BODY); - assertThat(issue.getName()).isEqualTo("id"); + assertThat(issue.getName()).isEqualTo("/id"); assertThat(issue.getValue()).isEqualTo("one two three"); assertThat(issue.getDetail()).isEqualTo("not a valid `int` value"); }); diff --git a/src/main/asciidoc/index.adoc b/src/main/asciidoc/index.adoc index baa784d8..7e389608 100644 --- a/src/main/asciidoc/index.adoc +++ b/src/main/asciidoc/index.adoc @@ -822,6 +822,50 @@ Scan additional packages for @ProblemType annotations. This configuration property only applies to *Spring Boot*. On Jakarta EE and Quarkus, all problem types are automatically picked up by CDI. +[[io.github.belgif.rest.problem.json-pointer-enabled]] +==== io.github.belgif.rest.problem.json-pointer-enabled + +Enable using JsonPointer syntax for the 'name' property for a schema violation issue in the request body. +Default `true`. + +[source,json] +---- +{ + "title": "Bad Request", + "status": 400,
type: + "urn:problem-type:belgif:badRequest",href:"https://www.belgif.be/specification/rest/api-guide/problems/badRequest.html", + "issues": [ + { + "type": "urn:problem-type:belgif:input-validation:schemaViolation", + "in": "body", + "name": "/person/0/ssin", + "detail": "An SSIN should be 11 digits long", + "value": "1234" + } + ] +} +---- + +If set to false, the JsonPath syntax is used. + +[source,json] +---- +{ + "title": "Bad Request", + "status": 400,
type: + "urn:problem-type:belgif:badRequest",href:"https://www.belgif.be/specification/rest/api-guide/problems/badRequest.html", + "issues": [ + { + "type": "urn:problem-type:belgif:input-validation:schemaViolation", + "in": "body", + "name": "person[0].ssin", + "detail": "An SSIN should be 11 digits long", + "value": "1234" + } + ] +} +---- + [[implementation-details]] == Implementation details diff --git a/src/main/asciidoc/release-notes.adoc b/src/main/asciidoc/release-notes.adoc index 48eedcfc..54427474 100644 --- a/src/main/asciidoc/release-notes.adoc +++ b/src/main/asciidoc/release-notes.adoc @@ -12,6 +12,12 @@ // tag::recent-versions[] +== Version 0.23 + +*belgif-rest-problem*: + +* Use <> by default for schema violation issues in request body. + == Version 0.22 *belgif-rest-problem*: