Skip to content

Commit

Permalink
Template extensions - always propagate errors when evaluating paramers
Browse files Browse the repository at this point in the history
- resolves quarkusio#22931
  • Loading branch information
mkouba committed Jan 17, 2022
1 parent 4bd4e90 commit 44d28dc
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 16 deletions.
4 changes: 2 additions & 2 deletions docs/src/main/asciidoc/qute-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1572,7 +1572,7 @@ TIP: A list element can be accessed directly: `{list.10}` or `{list[10]}`.
* `mod`: Modulo operation
** `{#if counter.mod(5) == 0}`

==== Strings
===== Strings

* `fmt` or `format`: format the string instance via `java.lang.String.format()`
** `{myStr.fmt("arg1","arg2")}`
Expand Down Expand Up @@ -1616,7 +1616,7 @@ TIP: A list element can be accessed directly: `{list.10}` or `{list[10]}`.
** `{time:format(myDate,'d MMM uuuu', myLocale)}`

* `time:format(dateTime,pattern,locale,timeZone)`: Formats temporal objects from the `java.time` package, `java.util.Date`, `java.util.Calendar` and `java.lang.Number`
** `{time:format(myDate,'d MMM uuuu',myTimeZoneId)}`
** `{time:format(myDate,'d MMM uuuu',myLocale,myTimeZoneId)}`

[[template_data]]
=== @TemplateData
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
package io.quarkus.qute.deployment.extensions;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.Map;

import javax.inject.Inject;

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

import io.quarkus.qute.Engine;
import io.quarkus.qute.Template;
import io.quarkus.qute.TemplateException;
import io.quarkus.test.QuarkusUnitTest;

public class TimeTemplateExtensionsTest {
Expand All @@ -29,6 +34,9 @@ public class TimeTemplateExtensionsTest {
@Inject
Template foo;

@Inject
Engine engine;

@Test
public void testFormat() {
Calendar nowCal = Calendar.getInstance();
Expand All @@ -44,4 +52,15 @@ public void testFormat() {
.data("myLocale", Locale.ENGLISH).render());
}

@Test
public void testInvalidParameter() {
try {
// input.birthday cannot be resolved
engine.parse("{time:format(input.birthday, 'uuuu')}").data("input", Map.of("name", "Quarkus Qute")).render();
fail();
} catch (TemplateException expected) {
assertTrue(expected.getMessage().startsWith("Property \"birthday\" not found on the base object"));
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ public class TimeTemplateExtensions {

private static final Map<Key, DateTimeFormatter> FORMATTER_CACHE = new ConcurrentHashMap<>();

public static void clearCache() {
FORMATTER_CACHE.clear();
}

static String format(TemporalAccessor temporal, String pattern) {
return FORMATTER_CACHE.computeIfAbsent(new Key(pattern, null, null), TimeTemplateExtensions::formatterForKey)
.format(temporal);
Expand Down Expand Up @@ -89,21 +93,23 @@ static final class Key {
private final String pattern;
private final Locale locale;
private final ZoneId timeZone;
private final int hashCode;

public Key(String pattern, Locale locale, ZoneId timeZone) {
this.pattern = pattern;
this.locale = locale;
this.timeZone = timeZone;
}

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((locale == null) ? 0 : locale.hashCode());
result = prime * result + ((pattern == null) ? 0 : pattern.hashCode());
result = prime * result + ((timeZone == null) ? 0 : timeZone.hashCode());
return result;
this.hashCode = result;
}

@Override
public int hashCode() {
return hashCode;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ private CompletedStage(T result, Throwable exception) {
this.exception = exception;
}

public boolean isFailure() {
return exception != null;
}

public T get() {
if (exception != null) {
// Throw an exception if completed exceptionally
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import java.util.concurrent.ExecutionException;
import java.util.function.Supplier;

@SuppressWarnings("rawtypes")
public final class EvaluatedParams {

static final EvaluatedParams EMPTY = new EvaluatedParams(CompletedStage.VOID, new Supplier<?>[0]);
Expand All @@ -29,12 +28,17 @@ public static EvaluatedParams evaluate(EvalContext context) {
Supplier<?>[] allResults = new Supplier[params.size()];
List<CompletableFuture<?>> asyncResults = null;
int i = 0;
CompletedStage<?> failure = null;
Iterator<Expression> it = params.iterator();
while (it.hasNext()) {
Expression expression = it.next();
CompletionStage<?> result = context.evaluate(expression);
if (result instanceof CompletedStage) {
allResults[i++] = (CompletedStage<?>) result;
CompletedStage<?> completed = (CompletedStage<?>) result;
allResults[i++] = completed;
if (completed.isFailure()) {
failure = completed;
}
// No async computation needed
continue;
} else {
Expand All @@ -48,7 +52,7 @@ public static EvaluatedParams evaluate(EvalContext context) {
}
CompletionStage<?> cs;
if (asyncResults == null) {
cs = CompletedStage.VOID;
cs = failure != null ? failure : CompletedStage.VOID;
} else if (asyncResults.size() == 1) {
cs = asyncResults.get(0);
} else {
Expand All @@ -71,18 +75,23 @@ public static EvaluatedParams evaluateMessageParams(EvalContext context) {
return EMPTY;
}
Supplier<?>[] allResults = new Supplier[params.size()];
List<CompletableFuture<Object>> asyncResults = null;
List<CompletableFuture<?>> asyncResults = null;

int i = 0;
CompletedStage<?> failure = null;
Iterator<Expression> it = params.subList(1, params.size()).iterator();
while (it.hasNext()) {
CompletionStage<Object> result = context.evaluate(it.next());
CompletionStage<?> result = context.evaluate(it.next());
if (result instanceof CompletedStage) {
allResults[i++] = (CompletedStage<Object>) result;
CompletedStage<?> completed = (CompletedStage<?>) result;
allResults[i++] = completed;
if (completed.isFailure()) {
failure = completed;
}
// No async computation needed
continue;
} else {
CompletableFuture<Object> fu = result.toCompletableFuture();
CompletableFuture<?> fu = result.toCompletableFuture();
if (asyncResults == null) {
asyncResults = new ArrayList<>();
}
Expand All @@ -92,7 +101,7 @@ public static EvaluatedParams evaluateMessageParams(EvalContext context) {
}
CompletionStage<?> cs;
if (asyncResults == null) {
cs = CompletedStage.VOID;
cs = failure != null ? failure : CompletedStage.VOID;
} else if (asyncResults.size() == 1) {
cs = asyncResults.get(0);
} else {
Expand All @@ -107,7 +116,7 @@ public static EvaluatedParams evaluateMessageParams(EvalContext context) {
EvaluatedParams(CompletionStage<?> stage) {
this.stage = stage;
if (stage instanceof CompletedStage) {
this.results = new Supplier[] { (CompletedStage) stage };
this.results = new Supplier[] { (CompletedStage<?>) stage };
} else {
this.results = new Supplier[] { Futures.toSupplier(stage.toCompletableFuture()) };
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

import java.util.List;
import java.util.Locale;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutionException;
import java.util.function.Supplier;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -32,7 +35,59 @@ public void testParameterTypesMatch() throws InterruptedException, ExecutionExce
assertFalse(new EvaluatedParams(null,
new Supplier[] { CompletedStage.of(10), CompletedStage.of("Foo") })
.parameterTypesMatch(false, new Class<?>[] { Integer.class, Object[].class }));
}

@Test
public void testInvalidParameter() {
EvaluatedParams params = EvaluatedParams.evaluate(new EvalContext() {

@Override
public List<Expression> getParams() {
Expression expr1 = ExpressionImpl.from("foo.bar");
Expression expr2 = ExpressionImpl.from("bar.baz");
return List.of(expr1, expr2);
}

@Override
public String getName() {
return null;
}

@Override
public Object getBase() {
return null;
}

@Override
public Object getAttribute(String key) {
return null;
}

@Override
public CompletionStage<Object> evaluate(Expression expression) {
if (expression.toOriginalString().equals("foo.bar")) {
return CompletedStage.of("foo");
} else if (expression.toOriginalString().equals("bar.baz")) {
return CompletedStage.failure(new IllegalArgumentException());
}
throw new IllegalStateException();
}

@Override
public CompletionStage<Object> evaluate(String expression) {
return null;
}
});
assertTrue(params.stage instanceof CompletedStage);
CompletedStage<?> completed = (CompletedStage<?>) params.stage;
assertTrue(completed.isFailure());
try {
completed.get();
fail();
} catch (TemplateException expected) {
Throwable cause = expected.getCause();
assertTrue(cause instanceof IllegalArgumentException);
}
}

}

0 comments on commit 44d28dc

Please sign in to comment.