diff --git a/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JsonDeserializerWithAccessFilter.java b/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JsonDeserializerWithAccessFilter.java index 3cc464cd..ba188b3c 100644 --- a/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JsonDeserializerWithAccessFilter.java +++ b/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JsonDeserializerWithAccessFilter.java @@ -163,7 +163,7 @@ public abstract class JsonDeserializerWithAccessFilter jsonFieldReader(node, field).readInto(dto); updatingFields.add(field); } catch (NoSuchFieldException e) { - throw new RuntimeException("setting field " + fieldName + " failed", e); + throw new BadRequestAlertException("Unknown property", fieldName, "unknownProperty"); } }); } diff --git a/src/main/java/org/hostsharing/hsadminng/service/util/ReflectionUtil.java b/src/main/java/org/hostsharing/hsadminng/service/util/ReflectionUtil.java index 574e098f..c8936c65 100644 --- a/src/main/java/org/hostsharing/hsadminng/service/util/ReflectionUtil.java +++ b/src/main/java/org/hostsharing/hsadminng/service/util/ReflectionUtil.java @@ -137,9 +137,24 @@ public class ReflectionUtil { T get() throws Exception; } - public static T unchecked(final ThrowingSupplier supplier) { + /** + * Catches checked exceptions and wraps these into an unchecked RuntimeException. + *

+ * Rationale: Checked exceptions are a controversial Java feature to begin with. + * They often mix error handling code into the normal flow of domain rules + * or other technical aspects which is not only hard to read but also violates + * the Single Responsibility Principle. Often this is even worse for expressions + * than it is for statements. + *

+ * + * @param expression an expresion which returns a T and may throw a checked exception + * @param the result type of the expression + * @return the result of the expression + * @throws RuntimeException which wraps a checked exception thrown by the expression + */ + public static T unchecked(final ThrowingSupplier expression) { try { - return supplier.get(); + return expression.get(); } catch (final Exception e) { throw new RuntimeException(e); } diff --git a/src/main/webapp/i18n/de/custom-error.json b/src/main/webapp/i18n/de/custom-error.json index 1199f245..b62e6f9c 100644 --- a/src/main/webapp/i18n/de/custom-error.json +++ b/src/main/webapp/i18n/de/custom-error.json @@ -1,6 +1,7 @@ { "error": { "idNotFound": "Technische Datensatz-ID nicht gefunden", + "unknownProperty": "Unbekannte Eigenschaft", "shareSubscriptionPositiveQuantity": "Zeichnungen von Geschäftsanteilen erfordern eine positive Stückzahl", "shareCancellationNegativeQuantity": "Kündigungen von Geschäftsanteilen erfordern eine negative Stückzahl", "shareTransactionImmutable": "Transaktionen mit Geschäftsanteilen sind unveränderlich", diff --git a/src/main/webapp/i18n/en/custom-error.json b/src/main/webapp/i18n/en/custom-error.json index 8373449d..3ca13d6c 100644 --- a/src/main/webapp/i18n/en/custom-error.json +++ b/src/main/webapp/i18n/en/custom-error.json @@ -1,6 +1,7 @@ { "error": { "idNotFound": "Technical record-ID not found", + "unknownProperty": "Unknown Property", "shareSubscriptionPositiveQuantity": "Share subscriptions require a positive quantity", "shareCancellationNegativeQuantity": "Share cancellations require a negative quantity", "shareTransactionImmutable": "Share transactions are immutable", diff --git a/src/test/java/org/hostsharing/hsadminng/service/accessfilter/JSonDeserializationWithAccessFilterUnitTest.java b/src/test/java/org/hostsharing/hsadminng/service/accessfilter/JSonDeserializationWithAccessFilterUnitTest.java index d4eb3722..af7dc287 100644 --- a/src/test/java/org/hostsharing/hsadminng/service/accessfilter/JSonDeserializationWithAccessFilterUnitTest.java +++ b/src/test/java/org/hostsharing/hsadminng/service/accessfilter/JSonDeserializationWithAccessFilterUnitTest.java @@ -399,6 +399,24 @@ public class JSonDeserializationWithAccessFilterUnitTest { .hasMessageContaining("GivenDtoWithUnknownFieldType.unknown"); } + @Test + public void shouldDetectUnknownPropertyName() throws IOException { + // given + securityContext.havingAuthenticatedUser().withAuthority(AuthoritiesConstants.ADMIN); + givenJSonTree(asJSon(ImmutablePair.of("somePropWhichDoesNotExist", "Some Value"))); + + // when + final Throwable exception = catchThrowable( + () -> deserializerForGivenDto().deserialize(jsonParser, null)); + + // then + assertThat(exception).isInstanceOfSatisfying(BadRequestAlertException.class, (exc) -> { + assertThat(exc).hasMessageStartingWith("Unknown property"); + assertThat(exc.getParam()).isEqualTo("somePropWhichDoesNotExist"); + assumeThat(exc.getErrorKey()).isEqualTo("unknownProperty"); + }); + } + // --- only fixture code below --- private void givenJSonTree(String givenJSon) throws IOException {