fix: enhance global key validation and update template parameters

This commit is contained in:
Relism
2026-04-29 10:31:14 +02:00
parent 0e2dad23e5
commit f310646868
6 changed files with 26 additions and 30 deletions
@@ -18,9 +18,13 @@ public abstract class BaseViewExtension<TTarget> implements FlashExtension {
if (k.isEmpty()) {
throw new IllegalArgumentException("global key must not be blank");
}
if (k.equals("global") || k.contains(".")) {
if (k.contains(".")) {
throw new IllegalArgumentException("global key must be a simple key (no dots), received: " + key);
}
boolean duplicate = globals.stream().anyMatch(g -> g.key().equals(k));
if (duplicate) {
throw new IllegalArgumentException("global key '" + k + "' is already registered");
}
globals.add(new GlobalValue(k, Objects.requireNonNull(resolver, "global resolver must not be null")));
return this;
}
@@ -1,11 +1,10 @@
package gg.jte.generated.ondemand.pages;
import dev.relism.flash.ext.view.jte.model.HomePage;
import java.util.Map;
@SuppressWarnings("unchecked")
public final class JtehomeGenerated {
public static final String JTE_NAME = "pages/home.jte";
public static final int[] JTE_LINE_INFO = {0,0,1,2,2,2,2,6,6,6,6,7,7,7,8,8,8,9,9,9,10,10,10,2,3,4,4,4,4};
public static void render(gg.jte.html.HtmlTemplateOutput jteOutput, gg.jte.html.HtmlInterceptor jteHtmlInterceptor, HomePage page, String build, Map<String, Object> global) {
public static final int[] JTE_LINE_INFO = {0,0,1,1,1,1,5,5,5,5,6,6,6,7,7,7,8,8,8,8,8,8,1,2,3,3,3,3};
public static void render(gg.jte.html.HtmlTemplateOutput jteOutput, gg.jte.html.HtmlInterceptor jteHtmlInterceptor, HomePage page, String build, String appName) {
jteOutput.writeContent("\n<h1>");
jteOutput.setContext("h1", null);
jteOutput.writeUserContent(page.title());
@@ -17,13 +16,13 @@ public final class JtehomeGenerated {
jteOutput.writeUserContent(build);
jteOutput.writeContent("</small>\n<small>");
jteOutput.setContext("small", null);
jteOutput.writeUserContent((String) global.get("appName"));
jteOutput.writeContent("</small>\n");
jteOutput.writeUserContent(appName);
jteOutput.writeContent("</small>");
}
public static void renderMap(gg.jte.html.HtmlTemplateOutput jteOutput, gg.jte.html.HtmlInterceptor jteHtmlInterceptor, java.util.Map<String, Object> params) {
HomePage page = (HomePage)params.get("page");
String build = (String)params.get("build");
Map<String, Object> global = (Map<String, Object>)params.get("global");
render(jteOutput, jteHtmlInterceptor, page, build, global);
String appName = (String)params.get("appName");
render(jteOutput, jteHtmlInterceptor, page, build, appName);
}
}
@@ -22,7 +22,6 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
final class JteRuntime implements ViewRuntimeBridge<JteTarget> {
private static final String GLOBAL_NAMESPACE = "global";
private final JteSettings settings;
private final List<GlobalValue> globals;
@@ -60,30 +59,25 @@ final class JteRuntime implements ViewRuntimeBridge<JteTarget> {
}
private ViewModel merge(Request req, ViewModel local) {
LinkedHashMap<String, Object> values = null;
LinkedHashMap<String, Object> values = new LinkedHashMap<>();
if (!globals.isEmpty()) {
LinkedHashMap<String, Object> globalMap = new LinkedHashMap<>();
for (GlobalValue binding : globals) {
Object resolved = binding.resolver().apply(req);
globalMap.put(binding.key(), ViewModel.unwrapValue(resolved));
}
if (!globalMap.isEmpty()) {
values = new LinkedHashMap<>();
values.put(GLOBAL_NAMESPACE, Collections.unmodifiableMap(globalMap));
}
values.put(binding.key(), ViewModel.unwrapValue(resolved));
}
if (local != null) {
Map<String, Object> localMap = local.toMap();
if (localMap.containsKey(GLOBAL_NAMESPACE)) {
throw new IllegalStateException("ViewModel key 'global' is reserved for framework globals");
for (String key : localMap.keySet()) {
if (values.containsKey(key)) {
throw new IllegalStateException(
"ViewModel key '" + key + "' conflicts with a registered global");
}
}
if (values == null) return ViewModel.copyOf(local);
values.putAll(localMap);
}
if (values == null || values.isEmpty()) return ViewModel.empty();
if (values.isEmpty()) return ViewModel.empty();
return ViewModel.owned(values);
}
@@ -38,7 +38,7 @@ class JteRuntimeTest {
}
@Test
void render_rejectsReservedGlobalKeyInLocalModel() {
void render_rejectsConflictingLocalKey() {
JteRuntime runtime = new JteRuntime(
JteSettings.builder().templateRoot("/templates").developmentMode(true).build(),
List.of(new GlobalValue("appName", req -> "Flash"))
@@ -48,12 +48,12 @@ class JteRuntimeTest {
runtime.render(new JteHandler() {
@Override
public ViewModel render(Request req) {
return ViewModel.of("global", "bad");
return ViewModel.of("appName", "override"); // collide con global
}
}, new JteTarget("pages/home.jte", ContentType.TEXT_HTML), null, new Response(200, ContentType.JSON))
);
assertTrue(ex.getMessage().contains("reserved"));
assertTrue(ex.getMessage().contains("appName"));
}
@Test
@@ -1,10 +1,9 @@
@import dev.relism.flash.ext.view.jte.model.HomePage
@import java.util.Map
@param HomePage page
@param String build
@param Map<String, Object> global
@param String appName
<h1>${page.title()}</h1>
<p>${page.author()}</p>
<small>${build}</small>
<small>${(String) global.get("appName")}</small>
<small>${appName}</small>