This commit is contained in:
63
modules/core/deployment/pom.xml
Normal file
63
modules/core/deployment/pom.xml
Normal file
@@ -0,0 +1,63 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>fr.cnd.compositor</groupId>
|
||||
<artifactId>core-parent</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>core-deployment</artifactId>
|
||||
<name>Core - Deployment</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-arc-deployment</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-rest-jackson-deployment</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>fr.cnd.compositor</groupId>
|
||||
<artifactId>core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-junit5</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-junit-internal</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>default-compile</id>
|
||||
<configuration>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-extension-processor</artifactId>
|
||||
<version>${quarkus.version}</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,14 @@
|
||||
package fr.cnd.compositor.core.deployment;
|
||||
|
||||
import io.quarkus.deployment.annotations.BuildStep;
|
||||
import io.quarkus.deployment.builditem.FeatureBuildItem;
|
||||
|
||||
class CoreProcessor {
|
||||
|
||||
private static final String FEATURE = "core";
|
||||
|
||||
@BuildStep
|
||||
FeatureBuildItem feature() {
|
||||
return new FeatureBuildItem(FEATURE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package fr.cnd.compositor.blocks.blocks;
|
||||
|
||||
import fr.cnd.compositor.models.BlockRenderDefinition;
|
||||
import fr.cnd.compositor.blocks.specs.BlockTemplate;
|
||||
import io.smallrye.common.annotation.Identifier;
|
||||
import io.smallrye.mutiny.Uni;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
|
||||
/**
|
||||
* Implémentation de test pour {@link BlockTemplate}.
|
||||
*/
|
||||
@Identifier("test-block")
|
||||
@ApplicationScoped
|
||||
public class TestBlockTemplate implements BlockTemplate {
|
||||
|
||||
@Override
|
||||
public Uni<String> render(BlockRenderDefinition definition) {
|
||||
return Uni.createFrom().item("<div>Test Block</div>");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package fr.cnd.compositor.blocks.pebble;
|
||||
|
||||
import fr.cnd.compositor.models.BlockRenderDefinition;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import io.smallrye.mutiny.Uni;
|
||||
import jakarta.inject.Inject;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
@QuarkusTest
|
||||
class AbstractPebbleBlockTemplateTest {
|
||||
|
||||
@Inject
|
||||
PebbleBlockEngine engine;
|
||||
|
||||
@Test
|
||||
void shouldRenderStaticTemplate() {
|
||||
AbstractPebbleBlockTemplate template = createTemplate("<div>Hello</div>");
|
||||
|
||||
String result = template.render(BlockRenderDefinition.builder().build()).await().indefinitely();
|
||||
|
||||
assertEquals("<div>Hello</div>", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRenderTemplateWithContext() {
|
||||
AbstractPebbleBlockTemplate template = createTemplate("<div>Hello {{ name }}</div>");
|
||||
Map<String, Object> context = Map.of("name", "World");
|
||||
|
||||
String result = template.render(BlockRenderDefinition.builder()
|
||||
.inputs(context)
|
||||
.build()).await().indefinitely();
|
||||
|
||||
assertEquals("<div>Hello World</div>", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRenderTemplateWithEmptyContext() {
|
||||
AbstractPebbleBlockTemplate template = createTemplate("<span>Static content</span>");
|
||||
final Map<String, Object> context = new HashMap<>();
|
||||
|
||||
String result = template.render(BlockRenderDefinition.builder()
|
||||
.inputs(context)
|
||||
.build()).await().indefinitely();
|
||||
|
||||
assertEquals("<span>Static content</span>", result);
|
||||
}
|
||||
|
||||
private AbstractPebbleBlockTemplate createTemplate(String templateContent) {
|
||||
AbstractPebbleBlockTemplate template = new AbstractPebbleBlockTemplate() {
|
||||
@Override
|
||||
public Uni<String> getTemplate() {
|
||||
return Uni.createFrom().item(templateContent);
|
||||
}
|
||||
};
|
||||
template.setEngine(engine);
|
||||
return template;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package fr.cnd.compositor.blocks.pebble;
|
||||
|
||||
import fr.cnd.compositor.blocks.specs.BlockEngine;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import jakarta.inject.Inject;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Tests unitaires pour {@link PebbleBlockEngine}.
|
||||
* <p>
|
||||
* Cette classe vérifie le comportement du moteur de rendu Pebble
|
||||
* qui implémente l'interface {@link BlockEngine}.
|
||||
*/
|
||||
@QuarkusTest
|
||||
class PebbleBlockEngineTest {
|
||||
|
||||
@Inject
|
||||
PebbleBlockEngine pebbleBlockEngine;
|
||||
|
||||
@Test
|
||||
void shouldRenderSimpleTemplate() {
|
||||
String content = "Hello World";
|
||||
|
||||
String result = pebbleBlockEngine.render(content, Collections.emptyMap())
|
||||
.await().indefinitely();
|
||||
|
||||
assertEquals("Hello World", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRenderTemplateWithVariable() {
|
||||
String content = "Hello {{ name }}!";
|
||||
Map<String, Object> context = Map.of("name", "Guillaume");
|
||||
|
||||
String result = pebbleBlockEngine.render(content, context)
|
||||
.await().indefinitely();
|
||||
|
||||
assertEquals("Hello Guillaume!", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRenderTemplateWithMultipleVariables() {
|
||||
String content = "{{ greeting }} {{ name }}, you have {{ count }} messages.";
|
||||
Map<String, Object> context = Map.of(
|
||||
"greeting", "Bonjour",
|
||||
"name", "Alice",
|
||||
"count", 5
|
||||
);
|
||||
|
||||
String result = pebbleBlockEngine.render(content, context)
|
||||
.await().indefinitely();
|
||||
|
||||
assertEquals("Bonjour Alice, you have 5 messages.", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRenderTemplateWithConditional() {
|
||||
String content = "{% if active %}Active{% else %}Inactive{% endif %}";
|
||||
|
||||
String resultActive = pebbleBlockEngine.render(content, Map.of("active", true))
|
||||
.await().indefinitely();
|
||||
String resultInactive = pebbleBlockEngine.render(content, Map.of("active", false))
|
||||
.await().indefinitely();
|
||||
|
||||
assertEquals("Active", resultActive);
|
||||
assertEquals("Inactive", resultInactive);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRenderTemplateWithLoop() {
|
||||
String content = "{% for item in items %}{{ item }}{% if not loop.last %}, {% endif %}{% endfor %}";
|
||||
Map<String, Object> context = Map.of("items", java.util.List.of("A", "B", "C"));
|
||||
|
||||
String result = pebbleBlockEngine.render(content, context)
|
||||
.await().indefinitely();
|
||||
|
||||
assertEquals("A, B, C", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHandleEmptyContext() {
|
||||
String content = "Static content only";
|
||||
|
||||
String result = pebbleBlockEngine.render(content, Collections.emptyMap())
|
||||
.await().indefinitely();
|
||||
|
||||
assertEquals("Static content only", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHandleMissingVariableGracefully() {
|
||||
String content = "Hello {{ name }}!";
|
||||
|
||||
String result = pebbleBlockEngine.render(content, Collections.emptyMap())
|
||||
.await().indefinitely();
|
||||
|
||||
assertEquals("Hello !", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowExceptionForInvalidTemplate() {
|
||||
String content = "{% invalid syntax %}";
|
||||
|
||||
RuntimeException exception = assertThrows(RuntimeException.class, () ->
|
||||
pebbleBlockEngine.render(content, Collections.emptyMap())
|
||||
.await().indefinitely()
|
||||
);
|
||||
|
||||
assertTrue(exception.getMessage().contains("Failed to render template content"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package fr.cnd.compositor.blocks.resolvers;
|
||||
|
||||
import fr.cnd.compositor.models.BlockRenderDefinition;
|
||||
import fr.cnd.compositor.blocks.specs.BlockTemplate;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import jakarta.inject.Inject;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Tests unitaires pour {@link CDIBlockTemplateResolver}.
|
||||
* <p>
|
||||
* Cette classe vérifie le comportement du resolver qui recherche
|
||||
* les instances de {@link BlockTemplate} par leur annotation {@code @Identifier}.
|
||||
*/
|
||||
@QuarkusTest
|
||||
class CDIBlockTemplateResolverTest {
|
||||
|
||||
@Inject
|
||||
CDIBlockTemplateResolver resolver;
|
||||
|
||||
/**
|
||||
* Vérifie que le resolver retourne null lorsque le template n'existe pas.
|
||||
*/
|
||||
@Test
|
||||
void shouldReturnNullWhenTemplateNotFound() {
|
||||
BlockTemplate result = resolver.resolveTemplate("unknown-template").await().indefinitely();
|
||||
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie que le resolver trouve et retourne le template correspondant à l'identifiant.
|
||||
*/
|
||||
@Test
|
||||
void shouldResolveTemplateTemplateByIdentifier() {
|
||||
BlockTemplate result = resolver.resolveTemplate("test-block").await().indefinitely();
|
||||
|
||||
assertNotNull(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie que le template résolu peut effectuer un rendu.
|
||||
*/
|
||||
@Test
|
||||
void shouldResolveTemplateWorkingTemplate() {
|
||||
BlockTemplate result = resolver.resolveTemplate("test-block").await().indefinitely();
|
||||
|
||||
assertNotNull(result);
|
||||
String rendered = result.render(BlockRenderDefinition.builder().build()).await().indefinitely();
|
||||
assertEquals("<div>Test Block</div>", rendered);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package fr.cnd.compositor.blocks.services;
|
||||
|
||||
import fr.cnd.compositor.models.BlockRenderDefinition;
|
||||
import fr.cnd.compositor.blocks.specs.BlockTemplate;
|
||||
import fr.cnd.compositor.blocks.specs.BlockTemplateResolver;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import jakarta.inject.Inject;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Tests unitaires pour {@link BlockService}.
|
||||
* <p>
|
||||
* Cette classe vérifie le comportement du service de résolution des templates
|
||||
* qui parcourt les différents {@link BlockTemplateResolver}
|
||||
* pour trouver le premier template correspondant.
|
||||
*/
|
||||
@QuarkusTest
|
||||
class BlockServiceTest {
|
||||
|
||||
@Inject
|
||||
BlockService blockService;
|
||||
|
||||
/**
|
||||
* Vérifie que le service retourne null lorsque aucun resolver ne trouve le template.
|
||||
*/
|
||||
@Test
|
||||
void shouldReturnNullWhenNoResolverFindsTemplate() {
|
||||
BlockTemplate result = blockService.resolveTemplate("unknown-template").await().indefinitely();
|
||||
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie que le service trouve un template existant via les resolvers.
|
||||
*/
|
||||
@Test
|
||||
void shouldResolveTemplateExistingTemplate() {
|
||||
BlockTemplate result = blockService.resolveTemplate("test-block").await().indefinitely();
|
||||
|
||||
assertNotNull(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie que le template résolu peut effectuer un rendu correct.
|
||||
*/
|
||||
@Test
|
||||
void shouldResolveTemplateWorkingTemplate() {
|
||||
BlockTemplate result = blockService.resolveTemplate("test-block").await().indefinitely();
|
||||
|
||||
assertNotNull(result);
|
||||
String rendered = result.render(BlockRenderDefinition.builder().build()).await().indefinitely();
|
||||
assertEquals("<div>Test Block</div>", rendered);
|
||||
}
|
||||
|
||||
}
|
||||
98
modules/core/integration-tests/pom.xml
Normal file
98
modules/core/integration-tests/pom.xml
Normal file
@@ -0,0 +1,98 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>fr.cnd.compositor</groupId>
|
||||
<artifactId>core-parent</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>core-integration-tests</artifactId>
|
||||
<name>Core - Integration Tests</name>
|
||||
|
||||
<properties>
|
||||
<skipITs>true</skipITs>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-rest</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>fr.cnd.compositor</groupId>
|
||||
<artifactId>core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.rest-assured</groupId>
|
||||
<artifactId>rest-assured</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>build</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>integration-test</goal>
|
||||
<goal>verify</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<systemPropertyVariables>
|
||||
<native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path>
|
||||
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
|
||||
<maven.home>${maven.home}</maven.home>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>native-image</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>native</name>
|
||||
</property>
|
||||
</activation>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<skipTests>${native.surefire.skip}</skipTests>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<properties>
|
||||
<quarkus.package.jar.enabled>false</quarkus.package.jar.enabled>
|
||||
<skipITs>false</skipITs>
|
||||
<quarkus.native.enabled>true</quarkus.native.enabled>
|
||||
</properties>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package fr.cnd.compositor.core.it;
|
||||
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Path;
|
||||
|
||||
@Path("/core")
|
||||
@ApplicationScoped
|
||||
public class CoreResource {
|
||||
// add some rest methods here
|
||||
|
||||
@GET
|
||||
public String hello() {
|
||||
return "Hello core";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package fr.cnd.compositor.core.it;
|
||||
|
||||
import io.quarkus.test.junit.QuarkusIntegrationTest;
|
||||
|
||||
@QuarkusIntegrationTest
|
||||
public class CoreResourceIT extends CoreResourceTest {
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package fr.cnd.compositor.core.it;
|
||||
|
||||
import static io.restassured.RestAssured.given;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
|
||||
@QuarkusTest
|
||||
public class CoreResourceTest {
|
||||
|
||||
@Test
|
||||
public void testHelloEndpoint() {
|
||||
given()
|
||||
.when().get("/core")
|
||||
.then()
|
||||
.statusCode(200)
|
||||
.body(is("Hello core"));
|
||||
}
|
||||
}
|
||||
21
modules/core/pom.xml
Normal file
21
modules/core/pom.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>fr.cnd.compositor</groupId>
|
||||
<artifactId>builder</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<relativePath>../../builder/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<packaging>pom</packaging>
|
||||
<artifactId>core-parent</artifactId>
|
||||
<name>Core - Parent</name>
|
||||
|
||||
<modules>
|
||||
<module>deployment</module>
|
||||
<module>runtime</module>
|
||||
</modules>
|
||||
|
||||
</project>
|
||||
80
modules/core/runtime/pom.xml
Normal file
80
modules/core/runtime/pom.xml
Normal file
@@ -0,0 +1,80 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>fr.cnd.compositor</groupId>
|
||||
<artifactId>core-parent</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>core</artifactId>
|
||||
<name>Core - Runtime</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>fr.cnd.compositor</groupId>
|
||||
<artifactId>compositor</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-arc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-rest-jackson</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.pebbletemplates</groupId>
|
||||
<artifactId>pebble</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-extension-maven-plugin</artifactId>
|
||||
<version>${quarkus.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>compile</phase>
|
||||
<goals>
|
||||
<goal>extension-descriptor</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<deployment>${project.groupId}:${project.artifactId}-deployment:${project.version}</deployment>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>default-compile</id>
|
||||
<configuration>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>${lombok.version}</version>
|
||||
</path>
|
||||
<path>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-extension-processor</artifactId>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
<annotationProcessorPathsUseDepMgmt>true</annotationProcessorPathsUseDepMgmt>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,60 @@
|
||||
package fr.cnd.compositor.blocks;
|
||||
|
||||
import fr.cnd.compositor.models.BlockRenderRequest;
|
||||
import fr.cnd.compositor.models.BlockRenderResult;
|
||||
import fr.cnd.compositor.blocks.services.BlockService;
|
||||
import io.smallrye.mutiny.Uni;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Path("/blocks")
|
||||
public class BlockResource {
|
||||
|
||||
@Inject
|
||||
BlockService blockService;
|
||||
|
||||
@POST
|
||||
@Path("template/render")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Uni<BlockRenderResult> render(BlockRenderRequest request) {
|
||||
final Map<String, List<String>> result = new HashMap<>();
|
||||
Uni<Void> chain = Uni.createFrom().voidItem();
|
||||
for (final String definitionId : request.getDefinitions().keySet()) {
|
||||
final List<String> definitionResults = new ArrayList<>();
|
||||
result.put(definitionId, definitionResults);
|
||||
chain = chain.chain(() -> blockService.renderTemplate(request.getDefinitions().get(definitionId))
|
||||
.invoke(definitionResults::add))
|
||||
.replaceWithVoid();
|
||||
}
|
||||
|
||||
return chain.map(x -> BlockRenderResult.builder().result(result).build());
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("configuration/render")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Uni<BlockRenderResult> renderConfiguration(BlockRenderRequest request) {
|
||||
final Map<String, List<String>> result = new HashMap<>();
|
||||
Uni<Void> chain = Uni.createFrom().voidItem();
|
||||
for (final String definitionId : request.getDefinitions().keySet()) {
|
||||
final List<String> definitionResults = new ArrayList<>();
|
||||
result.put(definitionId, definitionResults);
|
||||
chain = chain.chain(() -> blockService.renderBlockConfiguration(request.getDefinitions().get(definitionId))
|
||||
.invoke(definitionResults::add))
|
||||
.replaceWithVoid();
|
||||
}
|
||||
|
||||
return chain.map(x -> BlockRenderResult.builder().result(result).build());
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package fr.cnd.compositor.blocks.models;
|
||||
|
||||
import lombok.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class BlockRenderDefinition {
|
||||
String name;
|
||||
Map<String, Object> inputs;
|
||||
Map<String, List<BlockRenderDefinition>> slots;
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package fr.cnd.compositor.blocks.models;
|
||||
|
||||
import lombok.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class BlockRenderRequest {
|
||||
Map<String, List<BlockRenderDefinition>> definitions;
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package fr.cnd.compositor.blocks.models;
|
||||
|
||||
import lombok.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class BlockRenderResult {
|
||||
Map<String, List<String>> result;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package fr.cnd.compositor.blocks.pebble;
|
||||
|
||||
import fr.cnd.compositor.blocks.models.BlockRenderDefinition;
|
||||
import fr.cnd.compositor.models.BlockRenderDefinition;
|
||||
import fr.cnd.compositor.blocks.specs.BlockTemplate;
|
||||
import io.smallrye.mutiny.Uni;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package fr.cnd.compositor.blocks.pebble;
|
||||
|
||||
import fr.cnd.compositor.blocks.specs.BlockEngine;
|
||||
import io.pebbletemplates.pebble.PebbleEngine;
|
||||
import io.pebbletemplates.pebble.template.PebbleTemplate;
|
||||
import io.smallrye.mutiny.Uni;
|
||||
import io.smallrye.mutiny.unchecked.Unchecked;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
import java.io.StringWriter;
|
||||
import java.util.Map;
|
||||
|
||||
@ApplicationScoped
|
||||
public class PebbleBlockEngine implements BlockEngine {
|
||||
|
||||
@Inject
|
||||
PebbleEngine pebbleEngine;
|
||||
|
||||
@Override
|
||||
public Uni<String> render(String content, Map<String, Object> context) {
|
||||
return Uni.createFrom().item(Unchecked.supplier(() -> {
|
||||
try {
|
||||
PebbleTemplate template = pebbleEngine.getTemplate(content);
|
||||
StringWriter writer = new StringWriter();
|
||||
template.evaluate(writer, context);
|
||||
return writer.toString();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to render template content", e);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package fr.cnd.compositor.blocks.pebble;
|
||||
|
||||
import io.pebbletemplates.pebble.PebbleEngine;
|
||||
import io.pebbletemplates.pebble.loader.StringLoader;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
public class PebbleProvider {
|
||||
@Inject
|
||||
PebbleBlocksExtension pebbleBlocksExtension;
|
||||
|
||||
@ApplicationScoped
|
||||
PebbleEngine createPebbleEngine() {
|
||||
return new PebbleEngine.Builder()
|
||||
.loader(new StringLoader())
|
||||
.extension(pebbleBlocksExtension)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package fr.cnd.compositor.blocks.resolvers;
|
||||
|
||||
import fr.cnd.compositor.blocks.specs.BlockConfiguration;
|
||||
import fr.cnd.compositor.blocks.specs.BlockConfigurationResolver;
|
||||
import io.smallrye.common.annotation.Identifier;
|
||||
import io.smallrye.mutiny.Uni;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.enterprise.inject.Any;
|
||||
import jakarta.enterprise.inject.Instance;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
@ApplicationScoped
|
||||
public class CDIBlockConfigurationResolver implements BlockConfigurationResolver {
|
||||
|
||||
@Inject
|
||||
@Any
|
||||
Instance<BlockConfiguration> blockConfigurations;
|
||||
|
||||
@Override
|
||||
public Uni<BlockConfiguration> resolveConfiguration(String blockIdentifier) {
|
||||
Instance<BlockConfiguration> selected = blockConfigurations.select(Identifier.Literal.of(blockIdentifier));
|
||||
if (selected.isResolvable()) {
|
||||
return Uni.createFrom().item(selected.get());
|
||||
}
|
||||
return Uni.createFrom().nullItem();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package fr.cnd.compositor.blocks.resolvers;
|
||||
|
||||
import fr.cnd.compositor.blocks.specs.BlockTemplate;
|
||||
import fr.cnd.compositor.blocks.specs.BlockTemplateResolver;
|
||||
import io.smallrye.common.annotation.Identifier;
|
||||
import io.smallrye.mutiny.Uni;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.enterprise.inject.Any;
|
||||
import jakarta.enterprise.inject.Instance;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
@ApplicationScoped
|
||||
public class CDIBlockTemplateResolver implements BlockTemplateResolver {
|
||||
|
||||
@Inject
|
||||
@Any
|
||||
Instance<BlockTemplate> blockTemplates;
|
||||
|
||||
@Override
|
||||
public Uni<BlockTemplate> resolveTemplate(String blockIdentifier) {
|
||||
Instance<BlockTemplate> selected = blockTemplates.select(Identifier.Literal.of(blockIdentifier));
|
||||
if (selected.isResolvable()) {
|
||||
return Uni.createFrom().item(selected.get());
|
||||
}
|
||||
return Uni.createFrom().nullItem();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package fr.cnd.compositor.blocks.resolvers;
|
||||
|
||||
import fr.cnd.compositor.blocks.specs.ContextMapping;
|
||||
import fr.cnd.compositor.blocks.specs.ContextMappingResolver;
|
||||
import io.smallrye.common.annotation.Identifier;
|
||||
import io.smallrye.mutiny.Uni;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.enterprise.inject.Any;
|
||||
import jakarta.enterprise.inject.Instance;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
@ApplicationScoped
|
||||
public class CDIContextMappingResolver implements ContextMappingResolver {
|
||||
|
||||
@Inject
|
||||
@Any
|
||||
Instance<ContextMapping> contextMappings;
|
||||
|
||||
@Override
|
||||
public Uni<ContextMapping> resolveContextMapping(String contextMappingIdentifier) {
|
||||
Instance<ContextMapping> selected = contextMappings.select(Identifier.Literal.of(contextMappingIdentifier));
|
||||
if (selected.isResolvable()) {
|
||||
return Uni.createFrom().item(selected.get());
|
||||
}
|
||||
return Uni.createFrom().nullItem();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
package fr.cnd.compositor.blocks.services;
|
||||
|
||||
import fr.cnd.compositor.models.BlockRenderDefinition;
|
||||
import fr.cnd.compositor.blocks.specs.BlockConfiguration;
|
||||
import fr.cnd.compositor.blocks.specs.BlockConfigurationResolver;
|
||||
import fr.cnd.compositor.blocks.specs.BlockTemplate;
|
||||
import fr.cnd.compositor.blocks.specs.BlockTemplateResolver;
|
||||
import io.quarkus.arc.All;
|
||||
import io.smallrye.mutiny.Uni;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Service responsible for resolving block templates by name.
|
||||
* <p>
|
||||
* This service aggregates all available {@link BlockTemplateResolver} implementations
|
||||
* and queries them sequentially until one returns a non-null template. This allows
|
||||
* multiple template sources (e.g., file-based, database-backed, remote) to coexist
|
||||
* with a chain-of-responsibility pattern.
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class BlockService {
|
||||
|
||||
@Inject
|
||||
@All
|
||||
List<BlockTemplateResolver> resolvers;
|
||||
|
||||
@Inject
|
||||
@All
|
||||
List<BlockConfigurationResolver> blockConfigurationResolvers;
|
||||
|
||||
/**
|
||||
* Resolves a block template by its name.
|
||||
* <p>
|
||||
* Iterates through all registered {@link BlockTemplateResolver} instances and returns
|
||||
* the first non-null {@link BlockTemplate} found. If no resolver can handle the template,
|
||||
* returns {@code null}.
|
||||
*
|
||||
* @param templateName the identifier of the template to resolve
|
||||
* @return a {@link Uni} emitting the resolved {@link BlockTemplate}, or {@code null} if not found
|
||||
*/
|
||||
public Uni<BlockTemplate> resolveTemplate(String templateName) {
|
||||
Uni<BlockTemplate> result = Uni.createFrom().nullItem();
|
||||
|
||||
for (BlockTemplateResolver resolver : resolvers) {
|
||||
result = result.chain(template -> {
|
||||
if (template != null) {
|
||||
return Uni.createFrom().item(template);
|
||||
}
|
||||
return resolver.resolveTemplate(templateName);
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a block configuration by its identifier.
|
||||
* <p>
|
||||
* Iterates through all registered {@link BlockConfigurationResolver} instances and returns
|
||||
* the first non-null {@link BlockConfiguration} found. If no resolver can handle the identifier,
|
||||
* returns {@code null}.
|
||||
*
|
||||
* @param blockIdentifier the identifier of the block configuration to resolve
|
||||
* @return a {@link Uni} emitting the resolved {@link BlockConfiguration}, or {@code null} if not found
|
||||
*/
|
||||
public Uni<BlockConfiguration> resolveBlockConfiguration(String blockIdentifier) {
|
||||
Uni<BlockConfiguration> result = Uni.createFrom().nullItem();
|
||||
|
||||
for (BlockConfigurationResolver resolver : blockConfigurationResolvers) {
|
||||
result = result.chain(configuration -> {
|
||||
if (configuration != null) {
|
||||
return Uni.createFrom().item(configuration);
|
||||
}
|
||||
return resolver.resolveConfiguration(blockIdentifier);
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public Uni<String> renderTemplate(List<BlockRenderDefinition> definitions) {
|
||||
final List<String> results = new ArrayList<>();
|
||||
Uni<Void> chain = Uni.createFrom().voidItem();
|
||||
for (BlockRenderDefinition definition : definitions) {
|
||||
chain = chain.chain(() -> renderTemplate(definition))
|
||||
.invoke(results::add)
|
||||
.replaceWithVoid();
|
||||
}
|
||||
|
||||
return chain.map(x -> String.join(" ", results));
|
||||
}
|
||||
|
||||
public Uni<String> renderTemplate(BlockRenderDefinition definition) {
|
||||
final Map<String, String> slotsContent = new HashMap<>();
|
||||
Uni<Void> slotChain = Uni.createFrom().voidItem();
|
||||
for (String slotName : Optional.ofNullable(definition.getSlots()).map(Map::keySet).orElse(new HashSet<>())) {
|
||||
slotChain = slotChain.chain(() -> renderTemplate(definition.getSlots().get(slotName)))
|
||||
.invoke(slotContent -> slotsContent.put(slotName, slotContent))
|
||||
.replaceWithVoid();
|
||||
}
|
||||
|
||||
return slotChain.chain(() -> resolveTemplate(definition.getName()))
|
||||
.chain(blockTemplate -> {
|
||||
final Map<String, Object> context = new HashMap<>(Optional.ofNullable(definition.getInputs()).orElse(new HashMap<>()));
|
||||
context.put("__slots__", slotsContent);
|
||||
return blockTemplate.render(BlockRenderDefinition.builder()
|
||||
.name(definition.getName())
|
||||
.inputs(context)
|
||||
.build());
|
||||
});
|
||||
}
|
||||
|
||||
public Uni<String> renderBlockConfiguration(List<BlockRenderDefinition> definitions) {
|
||||
return mapDefinition(definitions)
|
||||
.chain(this::renderTemplate);
|
||||
}
|
||||
|
||||
private Uni<List<BlockRenderDefinition>> mapDefinition(List<BlockRenderDefinition> definitions) {
|
||||
final List<BlockRenderDefinition> results = new ArrayList<>();
|
||||
Uni<Void> chain = Uni.createFrom().voidItem();
|
||||
for (BlockRenderDefinition definition : definitions) {
|
||||
chain = chain.chain(() -> mapDefinition(definition))
|
||||
.invoke(results::add)
|
||||
.replaceWithVoid();
|
||||
}
|
||||
|
||||
return chain.map(x -> results);
|
||||
}
|
||||
|
||||
private Uni<BlockRenderDefinition> mapDefinition(BlockRenderDefinition definition) {
|
||||
final Map<String, List<BlockRenderDefinition>> slotMapping = new HashMap<>();
|
||||
Uni<Void> slotChain = Uni.createFrom().voidItem();
|
||||
for (String slotName : Optional.ofNullable(definition.getSlots()).map(Map::keySet).orElse(new HashSet<>())) {
|
||||
final List<BlockRenderDefinition> slotDefinitions = new ArrayList<>();
|
||||
slotMapping.put(slotName, slotDefinitions);
|
||||
for (BlockRenderDefinition blockDefinition : definition.getSlots().get(slotName)) {
|
||||
slotChain = slotChain.chain(() -> mapDefinition(blockDefinition))
|
||||
.invoke(slotDefinitions::add)
|
||||
.replaceWithVoid();
|
||||
}
|
||||
}
|
||||
|
||||
final BlockRenderDefinition mappedDefinition = BlockRenderDefinition.builder()
|
||||
.slots(slotMapping)
|
||||
.build();
|
||||
|
||||
return slotChain.chain(() -> resolveBlockConfiguration(definition.getName())
|
||||
.call(blockConfiguration -> blockConfiguration
|
||||
.getBlockTemplateName()
|
||||
.invoke(mappedDefinition::setName))
|
||||
.call(blockConfiguration -> blockConfiguration
|
||||
.mapInputs(definition.getInputs())
|
||||
.invoke(mappedDefinition::setInputs))
|
||||
.map(x -> mappedDefinition));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package fr.cnd.compositor.blocks.specs;
|
||||
|
||||
import io.smallrye.mutiny.Uni;
|
||||
|
||||
public interface BlockConfigurationResolver {
|
||||
Uni<BlockConfiguration> resolveConfiguration(String blockIdentifier);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package fr.cnd.compositor.blocks.specs;
|
||||
|
||||
import io.smallrye.mutiny.Uni;
|
||||
import fr.cnd.compositor.blocks.pebble.PebbleBlockEngine;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* Interface définissant un moteur de rendu de templates pour les blocs.
|
||||
* <p>
|
||||
* Un {@code BlockEngine} est responsable de transformer un contenu template
|
||||
* en chaîne de caractères finale, en utilisant un contexte de données fourni.
|
||||
* Le rendu est effectué de manière asynchrone via {@link Uni}.
|
||||
* <p>
|
||||
* Les implémentations peuvent utiliser différents moteurs de templates
|
||||
* (Pebble, Freemarker, Thymeleaf, etc.) selon les besoins.
|
||||
*
|
||||
* <h2>Exemple d'implémentation</h2>
|
||||
* <pre>{@code
|
||||
* @ApplicationScoped
|
||||
* public class MyBlockEngine implements BlockEngine {
|
||||
*
|
||||
* @Override
|
||||
* public Uni<String> render(String content, Map<String, Object> context) {
|
||||
* return Uni.createFrom().item(() -> {
|
||||
* // Logique de rendu du template
|
||||
* return renderedContent;
|
||||
* });
|
||||
* }
|
||||
* }
|
||||
* }</pre>
|
||||
*
|
||||
* @see PebbleBlockEngine
|
||||
*/
|
||||
public interface BlockEngine {
|
||||
|
||||
/**
|
||||
* Effectue le rendu d'un contenu template avec le contexte fourni.
|
||||
* <p>
|
||||
* Cette méthode prend un template sous forme de chaîne de caractères
|
||||
* et le combine avec les données du contexte pour produire le résultat final.
|
||||
*
|
||||
* @param content le contenu du template à rendre, utilisant la syntaxe
|
||||
* spécifique au moteur de template implémenté
|
||||
* @param context une Map contenant les variables disponibles pour le template,
|
||||
* où les clés sont les noms des variables et les valeurs leurs données
|
||||
* @return un {@link Uni} émettant la chaîne de caractères résultant du rendu,
|
||||
* ou une erreur si le rendu échoue (template invalide, erreur d'évaluation, etc.)
|
||||
*/
|
||||
Uni<String> render(String content, Map<String, Object> context);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package fr.cnd.compositor.blocks.specs;
|
||||
|
||||
import fr.cnd.compositor.models.BlockRenderDefinition;
|
||||
import io.smallrye.mutiny.Uni;
|
||||
|
||||
public interface BlockTemplate {
|
||||
Uni<String> render(BlockRenderDefinition definition);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package fr.cnd.compositor.blocks.specs;
|
||||
|
||||
import io.smallrye.mutiny.Uni;
|
||||
|
||||
public interface BlockTemplateResolver {
|
||||
Uni<BlockTemplate> resolveTemplate(String blockIdentifier);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
name: Core
|
||||
#description: Do something useful.
|
||||
metadata:
|
||||
# keywords:
|
||||
# - core
|
||||
# guide: ... # To create and publish this guide, see https://github.com/quarkiverse/quarkiverse/wiki#documenting-your-extension
|
||||
# categories:
|
||||
# - "miscellaneous"
|
||||
# status: "preview"
|
||||
Reference in New Issue
Block a user