From e624cd5c2c8a7cf002c84b43c71b57f41fb43256 Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Mon, 18 Dec 2023 08:08:57 +0100 Subject: [PATCH 1/6] feat: Add enctype to Form Close: #674 --- .../test/resources/views/fieldset/form.html | 2 +- .../fields/tck/FormEncTypeRenderTest.java | 44 +++++++++++++++++++ .../java/io/micronaut/views/fields/Form.java | 39 +++++++++++++++- .../io/micronaut/views/fields/FormTest.java | 7 +++ 4 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 views-fieldset-tck/src/main/java/io/micronaut/views/fields/tck/FormEncTypeRenderTest.java diff --git a/test-suite-thymeleaf-fieldset/src/test/resources/views/fieldset/form.html b/test-suite-thymeleaf-fieldset/src/test/resources/views/fieldset/form.html index 80f00922d..7eb84cdf0 100644 --- a/test-suite-thymeleaf-fieldset/src/test/resources/views/fieldset/form.html +++ b/test-suite-thymeleaf-fieldset/src/test/resources/views/fieldset/form.html @@ -1 +1 @@ -
\ No newline at end of file +
\ No newline at end of file diff --git a/views-fieldset-tck/src/main/java/io/micronaut/views/fields/tck/FormEncTypeRenderTest.java b/views-fieldset-tck/src/main/java/io/micronaut/views/fields/tck/FormEncTypeRenderTest.java new file mode 100644 index 000000000..e632b6ace --- /dev/null +++ b/views-fieldset-tck/src/main/java/io/micronaut/views/fields/tck/FormEncTypeRenderTest.java @@ -0,0 +1,44 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.views.fields.tck; + +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import io.micronaut.views.ViewsRenderer; +import io.micronaut.views.fields.Fieldset; +import io.micronaut.views.fields.Form; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.Collections; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@MicronautTest(startApplication = false) +@SuppressWarnings({"java:S5960"}) // Assertions are fine, these are tests +public class FormEncTypeRenderTest { + + @Test + void render(ViewsRenderer, ?> viewsRenderer) throws IOException { + assertNotNull(viewsRenderer); + Form form = new Form("/foo/bar", "post", new Fieldset(Collections.emptyList(), Collections.emptyList()), "application/x-www-form-urlencoded"); + assertEquals(""" +
\ +
""", + TestUtils.render("fieldset/form.html", viewsRenderer, Map.of("form", form))); + } +} diff --git a/views-fieldset/src/main/java/io/micronaut/views/fields/Form.java b/views-fieldset/src/main/java/io/micronaut/views/fields/Form.java index 3c07e5292..fe3e2c8a0 100644 --- a/views-fieldset/src/main/java/io/micronaut/views/fields/Form.java +++ b/views-fieldset/src/main/java/io/micronaut/views/fields/Form.java @@ -17,6 +17,10 @@ import io.micronaut.core.annotation.Experimental; import io.micronaut.core.annotation.Introspected; +import io.micronaut.core.annotation.NonNull; +import io.micronaut.core.annotation.Nullable; +import io.micronaut.views.exceptions.ViewRenderingException; +import jakarta.validation.constraints.Pattern; /** * Representation of an HTML form. @@ -25,8 +29,41 @@ * @param action Form Action * @param method Form Method. For example `post` * @param fieldset Form fields + * @param enctype how the form-data should be encoded when submitting it to the server */ @Experimental @Introspected -public record Form(String action, String method, Fieldset fieldset) { +public record Form(@NonNull String action, + @Nullable @Pattern(regexp = "get|post") String method, + @NonNull Fieldset fieldset, + @Nullable @Pattern(regexp = "application/x-www-form-urlencoded|multipart/form-data|text/plain") String enctype) { + + private static final String POST = "post"; + + public Form(@NonNull String action, @NonNull String method, @NonNull Fieldset fieldset) { + this(action, method, fieldset, null); + } + + public Form(@NonNull String action, @NonNull Fieldset fieldset) { + this(action, POST, fieldset, null); + } + + public Form(@NonNull String action, + @NonNull Fieldset fieldset, + @Nullable @Pattern(regexp = "application/x-www-form-urlencoded|multipart/form-data|text/plain") String enctype) { + this(action, POST, fieldset, enctype); + } + + public Form(@NonNull String action, + @NonNull String method, + @NonNull Fieldset fieldset, + @Nullable @Pattern(regexp = "application/x-www-form-urlencoded|multipart/form-data|text/plain") String enctype) { + if (enctype != null && !method.equals(POST)) { + throw new IllegalArgumentException("enctype attribute can be used only if method equals post"); + } + this.action = action; + this.method = method; + this.fieldset = fieldset; + this.enctype = enctype; + } } diff --git a/views-fieldset/src/test/java/io/micronaut/views/fields/FormTest.java b/views-fieldset/src/test/java/io/micronaut/views/fields/FormTest.java index 66fe6544d..49fef78a1 100644 --- a/views-fieldset/src/test/java/io/micronaut/views/fields/FormTest.java +++ b/views-fieldset/src/test/java/io/micronaut/views/fields/FormTest.java @@ -3,6 +3,8 @@ import io.micronaut.core.beans.BeanIntrospection; import org.junit.jupiter.api.Test; +import java.util.Collections; + import static org.junit.jupiter.api.Assertions.*; class FormTest { @@ -11,4 +13,9 @@ void isAnnotatedWithIntrospected() { assertDoesNotThrow(() -> BeanIntrospection.getIntrospection(Form.class)); } + @Test + void formEnctypeMethodGet() { + assertThrows(IllegalArgumentException.class, () -> new Form("/foo/bar", "get", new Fieldset(Collections.emptyList(), Collections.emptyList()), "application/x-www-form-urlencoded")); + assertDoesNotThrow(() -> new Form("/foo/bar", "post", new Fieldset(Collections.emptyList(), Collections.emptyList()), "application/x-www-form-urlencoded")); + } } From 8c8589f0309822472f3ccfbc0e0a87e8327b487b Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Mon, 18 Dec 2023 08:11:06 +0100 Subject: [PATCH 2/6] remove pattern from constructor arguments --- .../src/main/java/io/micronaut/views/fields/Form.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/views-fieldset/src/main/java/io/micronaut/views/fields/Form.java b/views-fieldset/src/main/java/io/micronaut/views/fields/Form.java index fe3e2c8a0..1cc59018e 100644 --- a/views-fieldset/src/main/java/io/micronaut/views/fields/Form.java +++ b/views-fieldset/src/main/java/io/micronaut/views/fields/Form.java @@ -50,14 +50,14 @@ public Form(@NonNull String action, @NonNull Fieldset fieldset) { public Form(@NonNull String action, @NonNull Fieldset fieldset, - @Nullable @Pattern(regexp = "application/x-www-form-urlencoded|multipart/form-data|text/plain") String enctype) { + @Nullable String enctype) { this(action, POST, fieldset, enctype); } public Form(@NonNull String action, @NonNull String method, @NonNull Fieldset fieldset, - @Nullable @Pattern(regexp = "application/x-www-form-urlencoded|multipart/form-data|text/plain") String enctype) { + @Nullable String enctype) { if (enctype != null && !method.equals(POST)) { throw new IllegalArgumentException("enctype attribute can be used only if method equals post"); } From 82d38cacecc21d7b2546b5b92c4d7f409a7e32a1 Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Mon, 18 Dec 2023 08:23:37 +0100 Subject: [PATCH 3/6] fix checkstyle --- .../io/micronaut/views/fields/tck/FormEncTypeRenderTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/views-fieldset-tck/src/main/java/io/micronaut/views/fields/tck/FormEncTypeRenderTest.java b/views-fieldset-tck/src/main/java/io/micronaut/views/fields/tck/FormEncTypeRenderTest.java index e632b6ace..99131b41e 100644 --- a/views-fieldset-tck/src/main/java/io/micronaut/views/fields/tck/FormEncTypeRenderTest.java +++ b/views-fieldset-tck/src/main/java/io/micronaut/views/fields/tck/FormEncTypeRenderTest.java @@ -28,9 +28,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -@MicronautTest(startApplication = false) + @SuppressWarnings({"java:S5960"}) // Assertions are fine, these are tests -public class FormEncTypeRenderTest { +@MicronautTest(startApplication = false) +class FormEncTypeRenderTest { @Test void render(ViewsRenderer, ?> viewsRenderer) throws IOException { From bc9d72b2222111e568a4e400fc9d6968eed73cc5 Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Mon, 18 Dec 2023 10:59:51 +0100 Subject: [PATCH 4/6] add validation test --- .../io/micronaut/views/fields/Fieldset.java | 6 +- .../java/io/micronaut/views/fields/Form.java | 56 +++++++++++-------- .../constraints/EnctypePostRequired.java | 43 ++++++++++++++ .../EnctypePostRequiredValidator.java | 42 ++++++++++++++ .../fields/constraints/FormMessages.java | 37 ++++++++++++ .../fields/constraints/package-info.java | 21 +++++++ .../views/fields/messages/Message.java | 4 +- .../io/micronaut/views/fields/FormTest.java | 45 ++++++++++++++- 8 files changed, 224 insertions(+), 30 deletions(-) create mode 100644 views-fieldset/src/main/java/io/micronaut/views/fields/constraints/EnctypePostRequired.java create mode 100644 views-fieldset/src/main/java/io/micronaut/views/fields/constraints/EnctypePostRequiredValidator.java create mode 100644 views-fieldset/src/main/java/io/micronaut/views/fields/constraints/FormMessages.java create mode 100644 views-fieldset/src/main/java/io/micronaut/views/fields/constraints/package-info.java diff --git a/views-fieldset/src/main/java/io/micronaut/views/fields/Fieldset.java b/views-fieldset/src/main/java/io/micronaut/views/fields/Fieldset.java index 00f4b0871..00ae083fc 100644 --- a/views-fieldset/src/main/java/io/micronaut/views/fields/Fieldset.java +++ b/views-fieldset/src/main/java/io/micronaut/views/fields/Fieldset.java @@ -20,6 +20,8 @@ import io.micronaut.core.annotation.NonNull; import io.micronaut.core.util.CollectionUtils; import io.micronaut.views.fields.messages.Message; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; import java.util.List; @@ -34,8 +36,8 @@ */ @Experimental @Introspected -public record Fieldset(@NonNull List fields, - @NonNull List errors) { +public record Fieldset(@NonNull @NotEmpty List<@Valid ? extends FormElement> fields, + @NonNull List<@Valid Message> errors) { /** * diff --git a/views-fieldset/src/main/java/io/micronaut/views/fields/Form.java b/views-fieldset/src/main/java/io/micronaut/views/fields/Form.java index 1cc59018e..d2fa90377 100644 --- a/views-fieldset/src/main/java/io/micronaut/views/fields/Form.java +++ b/views-fieldset/src/main/java/io/micronaut/views/fields/Form.java @@ -15,11 +15,11 @@ */ package io.micronaut.views.fields; -import io.micronaut.core.annotation.Experimental; -import io.micronaut.core.annotation.Introspected; -import io.micronaut.core.annotation.NonNull; -import io.micronaut.core.annotation.Nullable; -import io.micronaut.views.exceptions.ViewRenderingException; +import io.micronaut.core.annotation.*; +import io.micronaut.views.fields.constraints.EnctypePostRequired; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Pattern; /** @@ -27,43 +27,51 @@ * @author Sergio del Amo * @since 4.1.0 * @param action Form Action - * @param method Form Method. For example `post` + * @param method Form Method. either `get` or `post` * @param fieldset Form fields * @param enctype how the form-data should be encoded when submitting it to the server */ @Experimental +@EnctypePostRequired @Introspected -public record Form(@NonNull String action, - @Nullable @Pattern(regexp = "get|post") String method, - @NonNull Fieldset fieldset, +public record Form(@NonNull @NotBlank String action, + @NonNull @NotBlank @Pattern(regexp = "get|post") String method, + @NonNull @NotNull @Valid Fieldset fieldset, @Nullable @Pattern(regexp = "application/x-www-form-urlencoded|multipart/form-data|text/plain") String enctype) { private static final String POST = "post"; - public Form(@NonNull String action, @NonNull String method, @NonNull Fieldset fieldset) { + /** + * + * @param action Form Action + * @param method Form Method. either `get` or `post` + * @param fieldset Form fields + */ + public Form(@NonNull String action, + @NonNull String method, + @NonNull Fieldset fieldset) { this(action, method, fieldset, null); } - public Form(@NonNull String action, @NonNull Fieldset fieldset) { + /** + * + * @param action Form Action + * @param fieldset Form fields + */ + public Form(@NonNull String action, + @NonNull Fieldset fieldset) { this(action, POST, fieldset, null); } + /** + * + * @param action Form Action + * @param fieldset Form fields + * @param enctype how the form-data should be encoded when submitting it to the server + */ public Form(@NonNull String action, @NonNull Fieldset fieldset, @Nullable String enctype) { this(action, POST, fieldset, enctype); } - - public Form(@NonNull String action, - @NonNull String method, - @NonNull Fieldset fieldset, - @Nullable String enctype) { - if (enctype != null && !method.equals(POST)) { - throw new IllegalArgumentException("enctype attribute can be used only if method equals post"); - } - this.action = action; - this.method = method; - this.fieldset = fieldset; - this.enctype = enctype; - } } diff --git a/views-fieldset/src/main/java/io/micronaut/views/fields/constraints/EnctypePostRequired.java b/views-fieldset/src/main/java/io/micronaut/views/fields/constraints/EnctypePostRequired.java new file mode 100644 index 000000000..fa8f06a92 --- /dev/null +++ b/views-fieldset/src/main/java/io/micronaut/views/fields/constraints/EnctypePostRequired.java @@ -0,0 +1,43 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.views.fields.constraints; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * The annotated {@link io.micronaut.views.fields.Form} must not have an encytpe declared if the method is not post. + * @author Sergio del Amo + * @since 5.1.0 + * + */ +@Constraint(validatedBy = EnctypePostRequiredValidator.class) +@Target({ElementType.TYPE, ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface EnctypePostRequired { + String MESSAGE = "io.micronaut.views.fields.constraints.EnctypePostRequired.message"; + + String message() default "{" + MESSAGE + "}"; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/views-fieldset/src/main/java/io/micronaut/views/fields/constraints/EnctypePostRequiredValidator.java b/views-fieldset/src/main/java/io/micronaut/views/fields/constraints/EnctypePostRequiredValidator.java new file mode 100644 index 000000000..4e4e62d4b --- /dev/null +++ b/views-fieldset/src/main/java/io/micronaut/views/fields/constraints/EnctypePostRequiredValidator.java @@ -0,0 +1,42 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.views.fields.constraints; + +import io.micronaut.core.annotation.AnnotationValue; +import io.micronaut.core.annotation.Introspected; +import io.micronaut.core.annotation.NonNull; +import io.micronaut.core.annotation.Nullable; +import io.micronaut.validation.validator.constraints.ConstraintValidator; +import io.micronaut.validation.validator.constraints.ConstraintValidatorContext; +import io.micronaut.views.fields.Form; + +/** + * Validator for the constraint {@link EnctypePostRequired} being applied to a {@link Form}. + * @author Sergio del Amo + * @since 5.1.0 + */ +@Introspected +public class EnctypePostRequiredValidator implements ConstraintValidator { + + private static final String METHOD_POST = "post"; + + @Override + public boolean isValid(@Nullable Form form, + @NonNull AnnotationValue annotationMetadata, + @NonNull ConstraintValidatorContext context) { + return form.enctype() == null || form.method().equals(METHOD_POST); + } +} diff --git a/views-fieldset/src/main/java/io/micronaut/views/fields/constraints/FormMessages.java b/views-fieldset/src/main/java/io/micronaut/views/fields/constraints/FormMessages.java new file mode 100644 index 000000000..65caf83af --- /dev/null +++ b/views-fieldset/src/main/java/io/micronaut/views/fields/constraints/FormMessages.java @@ -0,0 +1,37 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.views.fields.constraints; + +import io.micronaut.context.StaticMessageSource; +import io.micronaut.core.annotation.Internal; +import jakarta.inject.Singleton; + +/** + * Messages contributed by Form validation annotations. + * @author Sergio del Amo + * @since 5.1.0 + */ +@Singleton +@Internal +class FormMessages extends StaticMessageSource { + private static final String ENCTYPE_POST_REQUIRED_MESSAGE = "enctype attribute can be used only if method equals post"; + + private static final String MESSAGE_SUFFIX = ".message"; + + FormMessages() { + addMessage(EnctypePostRequired.class.getName() + MESSAGE_SUFFIX, ENCTYPE_POST_REQUIRED_MESSAGE); + } +} diff --git a/views-fieldset/src/main/java/io/micronaut/views/fields/constraints/package-info.java b/views-fieldset/src/main/java/io/micronaut/views/fields/constraints/package-info.java new file mode 100644 index 000000000..d938ff311 --- /dev/null +++ b/views-fieldset/src/main/java/io/micronaut/views/fields/constraints/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2017-2023 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Custom Constraints for validating HTML Forms. + * @author Sergio del Amo + * @since 5.1.0 + */ +package io.micronaut.views.fields.constraints; \ No newline at end of file diff --git a/views-fieldset/src/main/java/io/micronaut/views/fields/messages/Message.java b/views-fieldset/src/main/java/io/micronaut/views/fields/messages/Message.java index d92015cc5..2dadd8e1d 100644 --- a/views-fieldset/src/main/java/io/micronaut/views/fields/messages/Message.java +++ b/views-fieldset/src/main/java/io/micronaut/views/fields/messages/Message.java @@ -21,6 +21,7 @@ import io.micronaut.core.beans.BeanProperty; import io.micronaut.core.util.StringUtils; import jakarta.validation.ConstraintViolation; +import jakarta.validation.constraints.NotBlank; import java.util.ArrayList; import java.util.List; @@ -33,7 +34,8 @@ * @param code The i18n code which can be used to fetch a localized message. */ @Experimental -public record Message(@NonNull String defaultMessage, @Nullable String code) implements Comparable { +public record Message(@NonNull @NotBlank String defaultMessage, + @Nullable String code) implements Comparable { private static final String REGEX = "(.)([A-Z])"; private static final String REPLACEMENT = "$1 $2"; private static final String DOT = "."; diff --git a/views-fieldset/src/test/java/io/micronaut/views/fields/FormTest.java b/views-fieldset/src/test/java/io/micronaut/views/fields/FormTest.java index 49fef78a1..75a4ad1ab 100644 --- a/views-fieldset/src/test/java/io/micronaut/views/fields/FormTest.java +++ b/views-fieldset/src/test/java/io/micronaut/views/fields/FormTest.java @@ -1,12 +1,18 @@ package io.micronaut.views.fields; import io.micronaut.core.beans.BeanIntrospection; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import io.micronaut.validation.validator.Validator; +import io.micronaut.views.fields.elements.InputSubmitFormElement; +import jakarta.validation.ConstraintViolation; import org.junit.jupiter.api.Test; import java.util.Collections; +import java.util.Set; import static org.junit.jupiter.api.Assertions.*; +@MicronautTest(startApplication = false) class FormTest { @Test void isAnnotatedWithIntrospected() { @@ -14,8 +20,41 @@ void isAnnotatedWithIntrospected() { } @Test - void formEnctypeMethodGet() { - assertThrows(IllegalArgumentException.class, () -> new Form("/foo/bar", "get", new Fieldset(Collections.emptyList(), Collections.emptyList()), "application/x-www-form-urlencoded")); - assertDoesNotThrow(() -> new Form("/foo/bar", "post", new Fieldset(Collections.emptyList(), Collections.emptyList()), "application/x-www-form-urlencoded")); + void formValidation(Validator validator) { + Fieldset fieldset = new Fieldset(Collections.singletonList(new InputSubmitFormElement(FormGenerator.SUBMIT)), Collections.emptyList()); + // action cannot be an empty string + assertFalse(validator.validate(new Form("", "post", fieldset, "application/x-www-form-urlencoded")).isEmpty()); + // action cannot be null + assertFalse(validator.validate(new Form(null, "post", fieldset, "application/x-www-form-urlencoded")).isEmpty()); + + // method cannot be an empty string + assertFalse(validator.validate(new Form("/foo/bar", "", fieldset, null)).isEmpty()); + // method cannot be null + assertFalse(validator.validate(new Form("/foo/bar", null, fieldset, null)).isEmpty()); + // method can only be get or post + assertFalse(validator.validate(new Form("/foo/bar", "put", fieldset, null)).isEmpty()); + + //method cannot be get if enctype + Set> violations = validator.validate(new Form("/foo/bar", "get", fieldset, "application/x-www-form-urlencoded")); + assertFalse(violations.isEmpty()); + assertEquals(Collections.singletonList("enctype attribute can be used only if method equals post"), violations.stream().map(ConstraintViolation::getMessage).toList()); + + // fieldset cannot be null + Fieldset invalidFieldset = new Fieldset(Collections.emptyList(), Collections.emptyList()); + assertFalse(validator.validate(new Form("/foo/bar", "post", invalidFieldset, "application/x-www-form-urlencoded")).isEmpty()); + + // fieldset must be valid + assertFalse(validator.validate(new Form("/foo/bar", "post", null, "application/x-www-form-urlencoded")).isEmpty()); + + // enctype has to be form-url-enconded form-data or txt + assertFalse(validator.validate(new Form("/foo/bar", "post", fieldset, "text/html")).isEmpty()); + + // enctype can be null + assertTrue(validator.validate(new Form("/foo/bar", "post", fieldset, null)).isEmpty()); + assertTrue(validator.validate(new Form("/foo/bar", "post", fieldset, "application/x-www-form-urlencoded")).isEmpty()); + assertTrue(validator.validate(new Form("/foo/bar", "post", fieldset, "multipart/form-data")).isEmpty()); + assertTrue(validator.validate(new Form("/foo/bar", "post", fieldset, "text/plain")).isEmpty()); + assertTrue(validator.validate(new Form("/foo/bar", "get", fieldset, null)).isEmpty()); + } } From 90de7e8555a88dc215286e1d878ee5f63244f59a Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Mon, 18 Dec 2023 11:05:22 +0100 Subject: [PATCH 5/6] validate message --- .../views/fields/messages/Message.java | 2 ++ .../views/fields/messages/MessageTest.java | 20 +++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 views-fieldset/src/test/java/io/micronaut/views/fields/messages/MessageTest.java diff --git a/views-fieldset/src/main/java/io/micronaut/views/fields/messages/Message.java b/views-fieldset/src/main/java/io/micronaut/views/fields/messages/Message.java index 2dadd8e1d..270aab56a 100644 --- a/views-fieldset/src/main/java/io/micronaut/views/fields/messages/Message.java +++ b/views-fieldset/src/main/java/io/micronaut/views/fields/messages/Message.java @@ -16,6 +16,7 @@ package io.micronaut.views.fields.messages; import io.micronaut.core.annotation.Experimental; +import io.micronaut.core.annotation.Introspected; import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; import io.micronaut.core.beans.BeanProperty; @@ -34,6 +35,7 @@ * @param code The i18n code which can be used to fetch a localized message. */ @Experimental +@Introspected public record Message(@NonNull @NotBlank String defaultMessage, @Nullable String code) implements Comparable { private static final String REGEX = "(.)([A-Z])"; diff --git a/views-fieldset/src/test/java/io/micronaut/views/fields/messages/MessageTest.java b/views-fieldset/src/test/java/io/micronaut/views/fields/messages/MessageTest.java new file mode 100644 index 000000000..5a3a2459a --- /dev/null +++ b/views-fieldset/src/test/java/io/micronaut/views/fields/messages/MessageTest.java @@ -0,0 +1,20 @@ +package io.micronaut.views.fields.messages; + +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import io.micronaut.validation.validator.Validator; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +@MicronautTest(startApplication = false) +class MessageTest { + + @Test + void messageValidation(Validator validator) { + assertTrue(validator.validate(Message.of("Foo")).isEmpty()); + assertTrue(validator.validate(Message.of("Foo", "foo.code")).isEmpty()); + assertFalse(validator.validate(Message.of("")).isEmpty()); + String msg = null; + assertFalse(validator.validate(Message.of(msg)).isEmpty()); + } +} \ No newline at end of file From 1126d21616847e878a66e58109a46895d803de6d Mon Sep 17 00:00:00 2001 From: Tim Yates Date: Mon, 18 Dec 2023 10:22:54 +0000 Subject: [PATCH 6/6] Handle null in EnctypePostRequiredValidator --- .../views/fields/constraints/EnctypePostRequiredValidator.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/views-fieldset/src/main/java/io/micronaut/views/fields/constraints/EnctypePostRequiredValidator.java b/views-fieldset/src/main/java/io/micronaut/views/fields/constraints/EnctypePostRequiredValidator.java index 4e4e62d4b..02b997de4 100644 --- a/views-fieldset/src/main/java/io/micronaut/views/fields/constraints/EnctypePostRequiredValidator.java +++ b/views-fieldset/src/main/java/io/micronaut/views/fields/constraints/EnctypePostRequiredValidator.java @@ -37,6 +37,9 @@ public class EnctypePostRequiredValidator implements ConstraintValidator annotationMetadata, @NonNull ConstraintValidatorContext context) { + if (form == null) { + return true; + } return form.enctype() == null || form.method().equals(METHOD_POST); } }