fix(ext-view): clear cross-engine error, propagate globals across JteExtension builder chain
- BaseViewExtension: wrap the ViewRuntimeBridge provider collision with a message naming the real constraint (one view engine per FlashApp) instead of the generic "duplicate provider" error. - BaseViewExtension/JteExtension: carry registered globals across JteExtension's immutable settings builders (templateRoot/serveStatics/staticPrefix/withStaticCors/ staticCors). addGlobal() called before any of those used to be silently dropped, since each builder method returned a fresh instance with an empty globals list. - Correct flash-ext-view-jte docs (README, architecture.md, model-and-globals.md): globals merge flat with one typed @param per key, not under a global.* namespace like Thymeleaf — the docs previously claimed the same reserved namespace for both engines, which doesn't match JteRuntime's actual merge behavior. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
9de2800a83
commit
4feabc45d5
+24
-1
@@ -15,6 +15,23 @@ import java.util.function.Function;
|
||||
public abstract class BaseViewExtension<TTarget> implements FlashExtension {
|
||||
private final List<GlobalValue> globals = new ArrayList<>();
|
||||
|
||||
protected BaseViewExtension() {}
|
||||
|
||||
/**
|
||||
* Seeds this instance with globals carried over from a prior one. Subclasses whose
|
||||
* fluent settings methods return a new instance (e.g. {@code JteExtension.templateRoot(...)})
|
||||
* must route through this constructor — otherwise {@link #addGlobal} calls made before
|
||||
* such a method silently vanish, since the new instance would start with an empty list.
|
||||
*/
|
||||
protected BaseViewExtension(List<GlobalValue> seedGlobals) {
|
||||
globals.addAll(seedGlobals);
|
||||
}
|
||||
|
||||
/** Snapshot of globals registered so far — for subclasses to carry over into a new instance. */
|
||||
protected final List<GlobalValue> globals() {
|
||||
return List.copyOf(globals);
|
||||
}
|
||||
|
||||
public BaseViewExtension<TTarget> addGlobal(String key, Function<Request, Object> resolver) {
|
||||
String k = Objects.requireNonNull(key, "global key must not be null").trim();
|
||||
if (k.isEmpty()) {
|
||||
@@ -34,7 +51,13 @@ public abstract class BaseViewExtension<TTarget> implements FlashExtension {
|
||||
@Override
|
||||
public void configure(FlashRegistrar<?> app, FlashContext ctx) {
|
||||
ViewRuntimeBridge<TTarget> runtime = createRuntime(List.copyOf(globals));
|
||||
ctx.provide(ViewRuntimeBridge.class, runtime);
|
||||
try {
|
||||
ctx.provide(ViewRuntimeBridge.class, runtime);
|
||||
} catch (IllegalStateException e) {
|
||||
throw new IllegalStateException("Another flash-ext-view implementation is already installed in "
|
||||
+ "this FlashApp. Only one view engine (e.g. ThymeleafExtension or JteExtension, not both) "
|
||||
+ "can be active per app — install a single implementation.", e);
|
||||
}
|
||||
ctx.addAnnotationProcessor(handlerClass -> {
|
||||
validateHandlerClass(handlerClass);
|
||||
return List.of();
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package dev.relism.flash.ext.view.core;
|
||||
|
||||
import dev.relism.flash.extension.FlashContext;
|
||||
import dev.relism.flash.models.RequestHandler;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class BaseViewExtensionTest {
|
||||
|
||||
private static final class FakeViewExtension extends BaseViewExtension<Object> {
|
||||
@Override
|
||||
protected ViewRuntimeBridge<Object> createRuntime(List<GlobalValue> globals) {
|
||||
return new ViewRuntimeBridge<>() {
|
||||
@Override public Object resolve(Class<?> handlerClass) { return null; }
|
||||
@Override public RenderedView render(BaseViewHandler<Object> handler, Object target,
|
||||
dev.relism.flash.models.Request req,
|
||||
dev.relism.flash.models.Response res) { return null; }
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void validateHandlerClass(Class<? extends RequestHandler> handlerClass) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
void configure_twoViewEngines_failsWithClearMessage() {
|
||||
FlashContext ctx = new FlashContext();
|
||||
new FakeViewExtension().configure(null, ctx);
|
||||
|
||||
IllegalStateException ex = assertThrows(IllegalStateException.class,
|
||||
() -> new FakeViewExtension().configure(null, ctx));
|
||||
|
||||
assertTrue(ex.getMessage().contains("Only one view engine"),
|
||||
"expected a clear cross-engine message, got: " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user