From ecad79dded67c3a1b5fdb41ab55068837181555c Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Wed, 20 Jan 2021 10:27:33 +0100 Subject: [PATCH] Qute: add possibility to ignore parts of expressions during validation - resolves #12185 --- .../qute/deployment/QuteProcessor.java | 29 ++++++++++++-- .../typesafe/TypeCheckExcludesTest.java | 38 +++++++++++++++++++ .../io/quarkus/qute/runtime/QuteConfig.java | 19 +++++++++- 3 files changed, 82 insertions(+), 4 deletions(-) create mode 100644 extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/TypeCheckExcludesTest.java diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java index eae11f901ffa27..521480ccde6776 100644 --- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java +++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java @@ -993,11 +993,9 @@ TemplateVariantsBuildItem collectTemplateVariants(List te } @BuildStep - void excludeTypeChecks(BuildProducer excludes) { + void excludeTypeChecks(QuteConfig config, BuildProducer excludes) { // Exclude all checks that involve built-in value resolvers - // TODO: We need a better way to exclude value resolvers that are not template extension methods List skipOperators = Arrays.asList("?:", "or", ":", "?", "&&", "||"); - excludes.produce(new TypeCheckExcludeBuildItem(new Predicate() { @Override public boolean test(TypeCheck check) { @@ -1016,6 +1014,31 @@ public boolean test(TypeCheck check) { return false; } })); + + if (config.typeCheckExcludes.isPresent()) { + for (String exclude : config.typeCheckExcludes.get()) { + // + String[] parts = exclude.split("\\."); + if (parts.length < 2) { + // A valida exclude must have at least two parts + continue; + } + String className = Arrays.stream(parts).limit(parts.length - 1).collect(Collectors.joining(".")); + String propertyName = parts[parts.length - 1]; + excludes.produce(new TypeCheckExcludeBuildItem(new Predicate() { + @Override + public boolean test(TypeCheck check) { + if (!className.equals("*") && !check.clazz.name().toString().equals(className)) { + return false; + } + if (!propertyName.equals("*") && !check.name.equals(propertyName)) { + return false; + } + return true; + } + })); + } + } } @BuildStep diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/TypeCheckExcludesTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/TypeCheckExcludesTest.java new file mode 100644 index 00000000000000..7363cb94b0b649 --- /dev/null +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/TypeCheckExcludesTest.java @@ -0,0 +1,38 @@ +package io.quarkus.qute.deployment.typesafe; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import javax.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.qute.Template; +import io.quarkus.test.QuarkusUnitTest; + +public class TypeCheckExcludesTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(Movie.class, Machine.class, MachineStatus.class) + .addAsResource(new StringAsset("{@io.quarkus.qute.deployment.typesafe.Movie movie}" + + "{@io.quarkus.qute.deployment.typesafe.Machine machine}" + + "{movie.name}::{movie.superior}::{machine.ping}::{machine.neverEver}"), "templates/movie.html") + .addAsResource(new StringAsset( + "quarkus.qute.type-check-excludes=io.quarkus.qute.deployment.typesafe.Movie.superior,io.quarkus.qute.deployment.typesafe.Machine.*"), + "application.properties")); + + @Inject + Template movie; + + @Test + public void testValidationSuccess() { + assertEquals("Jason::NOT_FOUND::1::NOT_FOUND", + movie.data("movie", new Movie(), "machine", new Machine()).render()); + } + +} diff --git a/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/QuteConfig.java b/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/QuteConfig.java index afd3fe4438e3e6..f6a2d7c92159ce 100644 --- a/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/QuteConfig.java +++ b/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/QuteConfig.java @@ -2,6 +2,7 @@ import java.util.List; import java.util.Map; +import java.util.Optional; import io.quarkus.runtime.annotations.ConfigItem; import io.quarkus.runtime.annotations.ConfigPhase; @@ -11,7 +12,7 @@ public class QuteConfig { /** - * The set of suffixes used when attempting to locate a template file. + * The list of suffixes used when attempting to locate a template file. * * By default, `engine.getTemplate("foo")` would result in several lookups: `foo`, `foo.html`, `foo.txt`, etc. * @@ -27,4 +28,20 @@ public class QuteConfig { @ConfigItem public Map contentTypes; + /** + * The list of exclude rules used to intentionally ignore some parts of an expression when performing type-safe validation. + *

+ * An element value must have at least two parts separated by dot. The last part is used to match the property/method name. + * The prepended parts are used to match the class name. The value {@code *} can be used to match any name. + *

+ * Examples: + *

    + *
  • {@code org.acme.Foo.name} - exclude the property/method {@code name} on the {@code org.acme.Foo} class
  • + *
  • {@code org.acme.Foo.*} - exclude any property/method on the {@code org.acme.Foo} class
  • + *
  • {@code *.age} - exlude the property/method {@code age} on any class
  • + *
+ */ + @ConfigItem + public Optional> typeCheckExcludes; + }