Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Qute type-safe validation - honor the TemplateAttribute annotations #21787

Merged
merged 1 commit into from
Nov 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@
import io.quarkus.qute.generator.ExtensionMethodGenerator;
import io.quarkus.qute.generator.ExtensionMethodGenerator.NamespaceResolverCreator;
import io.quarkus.qute.generator.ExtensionMethodGenerator.NamespaceResolverCreator.ResolveCreator;
import io.quarkus.qute.generator.ExtensionMethodGenerator.Param;
import io.quarkus.qute.generator.ValueResolverGenerator;
import io.quarkus.qute.runtime.ContentTypes;
import io.quarkus.qute.runtime.EngineProducer;
Expand Down Expand Up @@ -1729,40 +1730,34 @@ private static TemplateExtensionMethodBuildItem findTemplateExtensionMethod(Info
// Name does not match
continue;
}
List<Type> parameters = extensionMethod.getMethod().parameters();
int realParamSize = parameters.size();
if (!extensionMethod.hasNamespace()) {
realParamSize -= 1;
}
if (TemplateExtension.ANY.equals(extensionMethod.getMatchName())) {
realParamSize -= 1;
}
if (realParamSize > 0 && !info.isVirtualMethod()) {

List<Param> evaluatedParams = extensionMethod.getParams().evaluated();
if (evaluatedParams.size() > 0 && !info.isVirtualMethod()) {
// If method accepts additional params the info must be a virtual method
continue;
}
if (info.isVirtualMethod()) {
// For virtual method validate the number of params and attempt to validate the parameter types if available
VirtualMethodPart virtualMethod = info.part.asVirtualMethod();

boolean isVarArgs = ValueResolverGenerator.isVarArgs(extensionMethod.getMethod());
int lastParamIdx = parameters.size() - 1;
int lastParamIdx = evaluatedParams.size() - 1;

if (isVarArgs) {
// For varargs methods match the minimal number of params
if ((realParamSize - 1) > virtualMethod.getParameters().size()) {
if ((evaluatedParams.size() - 1) > virtualMethod.getParameters().size()) {
continue;
}
} else {
if (virtualMethod.getParameters().size() != realParamSize) {
if (virtualMethod.getParameters().size() != evaluatedParams.size()) {
// Check number of parameters; some of params of the extension method must be ignored
continue;
}
}

// Check parameter types if available
boolean matches = true;
// Skip base and name param if needed
int idx = parameters.size() - realParamSize;
int idx = 0;

for (Expression param : virtualMethod.getParameters()) {

Expand All @@ -1772,9 +1767,9 @@ private static TemplateExtensionMethodBuildItem findTemplateExtensionMethod(Info
Type paramType;
if (isVarArgs && (idx >= lastParamIdx)) {
// Replace the type for varargs methods
paramType = parameters.get(lastParamIdx).asArrayType().component();
paramType = evaluatedParams.get(lastParamIdx).type.asArrayType().component();
} else {
paramType = parameters.get(idx);
paramType = evaluatedParams.get(idx).type;
}
if (!Types.isAssignableFrom(paramType,
result.type, index)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import io.quarkus.builder.item.MultiBuildItem;
import io.quarkus.qute.Namespaces;
import io.quarkus.qute.TemplateExtension;
import io.quarkus.qute.generator.ExtensionMethodGenerator;
import io.quarkus.qute.generator.ExtensionMethodGenerator.Parameters;

/**
* Represents a template extension method.
Expand All @@ -24,6 +26,7 @@ public final class TemplateExtensionMethodBuildItem extends MultiBuildItem {
private final Type matchType;
private final int priority;
private final String namespace;
private final Parameters params;

public TemplateExtensionMethodBuildItem(MethodInfo method, String matchName, String matchRegex, Type matchType,
int priority, String namespace) {
Expand All @@ -34,6 +37,7 @@ public TemplateExtensionMethodBuildItem(MethodInfo method, String matchName, Str
this.priority = priority;
this.namespace = (namespace != null && !namespace.isEmpty()) ? Namespaces.requireValid(namespace) : namespace;
this.matchPattern = (matchRegex == null || matchRegex.isEmpty()) ? null : Pattern.compile(matchRegex);
this.params = new ExtensionMethodGenerator.Parameters(method, matchPattern != null || matchesAny(), hasNamespace());
}

public MethodInfo getMethod() {
Expand Down Expand Up @@ -68,11 +72,19 @@ boolean matchesName(String name) {
if (matchPattern != null) {
return matchPattern.matcher(name).matches();
}
return TemplateExtension.ANY.equals(matchName) ? true : matchName.equals(name);
return matchesAny() ? true : matchName.equals(name);
}

boolean hasNamespace() {
boolean matchesAny() {
return TemplateExtension.ANY.equals(matchName);
}

public boolean hasNamespace() {
return namespace != null && !namespace.isEmpty();
}

public Parameters getParams() {
return params;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import javax.inject.Inject;

import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

Expand All @@ -19,6 +20,9 @@ public class TemplateExtensionAttributeTest {
@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addAsResource(
new StringAsset("{ping:transform('Foo')}"),
"templates/foo.txt")
.addClasses(Extensions.class));

@Inject
Expand All @@ -34,6 +38,8 @@ public void testTemplateExtensions() {
engine.parse("{foo.myAttr}").render());
assertEquals("OK",
engine.parse("{attr:ping}").instance().setAttribute("myAttribute", "OK").render());
assertEquals("foo::cs",
engine.getTemplate("foo").instance().setAttribute("locale", "cs").render());
}

@TemplateExtension
Expand All @@ -54,4 +60,13 @@ static String ping(@TemplateAttribute Object myAttribute) {

}

@TemplateExtension(namespace = "ping")
public static class NamespaceExtensions {

static String transform(@TemplateAttribute("locale") Object loc, String val) {
return val.toLowerCase() + "::" + loc.toString();
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@
String namespace() default "";

/**
* Used to annotated a template extension method parameter that should be obtained via
* Used to annotate a template extension method parameter that should be obtained via
* {@link TemplateInstance#getAttribute(String)}. The parameter type must be {@link java.lang.Object}.
*
* <pre>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -670,11 +670,11 @@ static Type box(Primitive primitive) {
}
}

static class Parameters implements Iterable<Param> {
public static final class Parameters implements Iterable<Param> {

final List<Param> params;

Parameters(MethodInfo method, boolean matchAnyOrRegex, boolean hasNamespace) {
public Parameters(MethodInfo method, boolean matchAnyOrRegex, boolean hasNamespace) {
List<Type> parameters = method.parameters();
Map<Integer, String> attributeParamNames = new HashMap<>();
for (AnnotationInstance annotation : method.annotations()) {
Expand Down Expand Up @@ -721,36 +721,36 @@ static class Parameters implements Iterable<Param> {
if (matchAnyOrRegex) {
Param nameParam = getFirst(ParamKind.NAME);
if (nameParam == null || !nameParam.type.name().equals(DotNames.STRING)) {
throw new IllegalStateException(
throw new TemplateException(
"Template extension method declared on " + method.declaringClass().name()
+ " must accept at least one string parameter to match the name: " + method);
}
}
if (!hasNamespace && getFirst(ParamKind.BASE) == null) {
throw new IllegalStateException(
throw new TemplateException(
"Template extension method declared on " + method.declaringClass().name()
+ " must accept at least one parameter to match the base object: " + method);
}

for (Param param : params) {
if (param.kind == ParamKind.ATTR && !param.type.name().equals(DotNames.OBJECT)) {
throw new IllegalStateException(
throw new TemplateException(
"Template extension method parameter annotated with @TemplateAttribute declared on "
+ method.declaringClass().name()
+ " must be of type java.lang.Object: " + method);
}
}
}

String[] parameterTypesAsStringArray() {
public String[] parameterTypesAsStringArray() {
String[] types = new String[params.size()];
for (int i = 0; i < params.size(); i++) {
types[i] = params.get(i).type.name().toString();
}
return types;
}

Param getFirst(ParamKind kind) {
public Param getFirst(ParamKind kind) {
for (Param param : params) {
if (param.kind == kind) {
return param;
Expand All @@ -759,15 +759,15 @@ Param getFirst(ParamKind kind) {
return null;
}

Param get(int index) {
public Param get(int index) {
return params.get(index);
}

int size() {
public int size() {
return params.size();
}

boolean needsEvaluation() {
public boolean needsEvaluation() {
for (Param param : params) {
if (param.kind == ParamKind.EVAL) {
return true;
Expand All @@ -776,7 +776,7 @@ boolean needsEvaluation() {
return false;
}

List<Param> evaluated() {
public List<Param> evaluated() {
if (params.isEmpty()) {
return Collections.emptyList();
}
Expand All @@ -796,12 +796,12 @@ public Iterator<Param> iterator() {

}

static class Param {
public static final class Param {

final String name;
final Type type;
final int position;
final ParamKind kind;
public final String name;
public final Type type;
public final int position;
public final ParamKind kind;

public Param(String name, Type type, int position, ParamKind paramKind) {
this.name = name;
Expand Down