value) {
+}
\ No newline at end of file
diff --git a/moqu/core/src/main/java/io/quarkiverse/openapi/moqu/model/Operation.java b/moqu/core/src/main/java/io/quarkiverse/openapi/moqu/model/Operation.java
new file mode 100644
index 000000000..bac321819
--- /dev/null
+++ b/moqu/core/src/main/java/io/quarkiverse/openapi/moqu/model/Operation.java
@@ -0,0 +1,10 @@
+package io.quarkiverse.openapi.moqu.model;
+
+/**
+ * Represents an HTTP operation.
+ *
+ *
+ * @param httpMethod the HTTP verb used for the current {@link Operation}.
+ */
+public record Operation(String httpMethod) {
+}
diff --git a/moqu/core/src/main/java/io/quarkiverse/openapi/moqu/model/Parameter.java b/moqu/core/src/main/java/io/quarkiverse/openapi/moqu/model/Parameter.java
new file mode 100644
index 000000000..ee87f789b
--- /dev/null
+++ b/moqu/core/src/main/java/io/quarkiverse/openapi/moqu/model/Parameter.java
@@ -0,0 +1,14 @@
+package io.quarkiverse.openapi.moqu.model;
+
+import io.quarkiverse.openapi.moqu.ParameterType;
+
+/**
+ * Represents an HTTP request parameter with a key, value, and location indicating where the parameter is used.
+ *
+ * @param key the key of the parameter (e.g., "id", "query").
+ * @param value the value of the parameter associated with the key.
+ * @param where the location of the parameter in the request (e.g., query string, path, header), defined by
+ * {@link ParameterType}.
+ */
+public record Parameter(String key, String value, ParameterType where) {
+}
\ No newline at end of file
diff --git a/moqu/core/src/main/java/io/quarkiverse/openapi/moqu/model/Request.java b/moqu/core/src/main/java/io/quarkiverse/openapi/moqu/model/Request.java
new file mode 100644
index 000000000..ff1b8fe8e
--- /dev/null
+++ b/moqu/core/src/main/java/io/quarkiverse/openapi/moqu/model/Request.java
@@ -0,0 +1,16 @@
+package io.quarkiverse.openapi.moqu.model;
+
+import java.util.Collection;
+
+/**
+ * Represents an HTTP request with essential details such as URL, HTTP method,
+ * example name, accepted header, and parameters.
+ *
+ * @param url the URL to which the request is sent.
+ * @param httpMethod the HTTP method (GET, POST, PUT, DELETE, etc.) used for the request.
+ * @param exampleName the name of the example associated with the request.
+ * @param accept the "Accept" header, which specifies the expected response format.
+ * @param parameters the list of parameters to be included in the request.
+ */
+public record Request(String url, String httpMethod, String exampleName, Header accept, Collection parameters) {
+}
diff --git a/moqu/core/src/main/java/io/quarkiverse/openapi/moqu/model/RequestResponsePair.java b/moqu/core/src/main/java/io/quarkiverse/openapi/moqu/model/RequestResponsePair.java
new file mode 100644
index 000000000..214cb92de
--- /dev/null
+++ b/moqu/core/src/main/java/io/quarkiverse/openapi/moqu/model/RequestResponsePair.java
@@ -0,0 +1,10 @@
+package io.quarkiverse.openapi.moqu.model;
+
+/**
+ * Represents a pair of an HTTP request and its corresponding response.
+ *
+ * @param request the HTTP request that was sent.
+ * @param response the HTTP response received for the given request.
+ */
+public record RequestResponsePair(Request request, Response response) {
+}
\ No newline at end of file
diff --git a/moqu/core/src/main/java/io/quarkiverse/openapi/moqu/model/Response.java b/moqu/core/src/main/java/io/quarkiverse/openapi/moqu/model/Response.java
new file mode 100644
index 000000000..532345b9f
--- /dev/null
+++ b/moqu/core/src/main/java/io/quarkiverse/openapi/moqu/model/Response.java
@@ -0,0 +1,20 @@
+package io.quarkiverse.openapi.moqu.model;
+
+import java.util.List;
+
+import io.swagger.v3.oas.models.media.MediaType;
+
+/**
+ * Represents an HTTP response with details such as the example name, media type,
+ * status code, content, and headers.
+ *
+ * @param exampleName the name of the example associated with this response.
+ * @param mediaType the media type of the response content (e.g., application/json, text/html),
+ * represented by {@link MediaType}.
+ * @param statusCode the HTTP status code of the response (e.g., 200, 404).
+ * @param content the body of the response as a string.
+ * @param headers the list of headers included in the response.
+ */
+public record Response(String exampleName, MediaType mediaType, int statusCode,
+ String content, List headers) {
+}
diff --git a/moqu/core/src/main/java/io/quarkiverse/openapi/moqu/wiremock/mapper/WiremockMapper.java b/moqu/core/src/main/java/io/quarkiverse/openapi/moqu/wiremock/mapper/WiremockMapper.java
new file mode 100644
index 000000000..83bf92e58
--- /dev/null
+++ b/moqu/core/src/main/java/io/quarkiverse/openapi/moqu/wiremock/mapper/WiremockMapper.java
@@ -0,0 +1,41 @@
+package io.quarkiverse.openapi.moqu.wiremock.mapper;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import io.quarkiverse.openapi.moqu.Moqu;
+import io.quarkiverse.openapi.moqu.MoquMapper;
+import io.quarkiverse.openapi.moqu.model.Request;
+import io.quarkiverse.openapi.moqu.model.RequestResponsePair;
+import io.quarkiverse.openapi.moqu.model.Response;
+import io.quarkiverse.openapi.moqu.wiremock.model.WiremockMapping;
+import io.quarkiverse.openapi.moqu.wiremock.model.WiremockRequest;
+import io.quarkiverse.openapi.moqu.wiremock.model.WiremockResponse;
+
+public class WiremockMapper implements MoquMapper {
+
+ @Override
+ public List map(Moqu moqu) {
+ ArrayList definitions = new ArrayList<>();
+ for (RequestResponsePair pair : moqu.getRequestResponsePairs()) {
+
+ Request mockRequest = pair.request();
+ Response mockResponse = pair.response();
+
+ WiremockRequest request = new WiremockRequest(mockRequest.httpMethod(), mockRequest.url());
+
+ Map headers = new HashMap<>();
+
+ mockResponse.headers().forEach(item -> headers.put(item.name(), item.value()));
+
+ WiremockResponse response = new WiremockResponse(mockResponse.statusCode(), mockResponse.content(), headers);
+
+ definitions.add(new WiremockMapping(
+ request, response));
+ }
+
+ return definitions;
+ }
+}
diff --git a/moqu/core/src/main/java/io/quarkiverse/openapi/moqu/wiremock/model/WiremockMapping.java b/moqu/core/src/main/java/io/quarkiverse/openapi/moqu/wiremock/model/WiremockMapping.java
new file mode 100644
index 000000000..bf703d7f9
--- /dev/null
+++ b/moqu/core/src/main/java/io/quarkiverse/openapi/moqu/wiremock/model/WiremockMapping.java
@@ -0,0 +1,4 @@
+package io.quarkiverse.openapi.moqu.wiremock.model;
+
+public record WiremockMapping(WiremockRequest request, WiremockResponse response) {
+}
\ No newline at end of file
diff --git a/moqu/core/src/main/java/io/quarkiverse/openapi/moqu/wiremock/model/WiremockRequest.java b/moqu/core/src/main/java/io/quarkiverse/openapi/moqu/wiremock/model/WiremockRequest.java
new file mode 100644
index 000000000..3d663e79a
--- /dev/null
+++ b/moqu/core/src/main/java/io/quarkiverse/openapi/moqu/wiremock/model/WiremockRequest.java
@@ -0,0 +1,6 @@
+package io.quarkiverse.openapi.moqu.wiremock.model;
+
+public record WiremockRequest(
+ String method,
+ String url) {
+}
diff --git a/moqu/core/src/main/java/io/quarkiverse/openapi/moqu/wiremock/model/WiremockResponse.java b/moqu/core/src/main/java/io/quarkiverse/openapi/moqu/wiremock/model/WiremockResponse.java
new file mode 100644
index 000000000..bfbcb6eef
--- /dev/null
+++ b/moqu/core/src/main/java/io/quarkiverse/openapi/moqu/wiremock/model/WiremockResponse.java
@@ -0,0 +1,8 @@
+package io.quarkiverse.openapi.moqu.wiremock.model;
+
+import java.util.Map;
+
+public record WiremockResponse(Integer status,
+ String body,
+ Map headers) {
+}
diff --git a/moqu/core/src/test/java/io/quarkiverse/openapi/moqu/OpenAPIMoquImporterTest.java b/moqu/core/src/test/java/io/quarkiverse/openapi/moqu/OpenAPIMoquImporterTest.java
new file mode 100644
index 000000000..8267b1ddd
--- /dev/null
+++ b/moqu/core/src/test/java/io/quarkiverse/openapi/moqu/OpenAPIMoquImporterTest.java
@@ -0,0 +1,181 @@
+package io.quarkiverse.openapi.moqu;
+
+import static io.quarkiverse.openapi.moqu.TestUtils.readContentFromFile;
+
+import java.util.List;
+import java.util.Map;
+
+import org.assertj.core.api.SoftAssertions;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import io.quarkiverse.openapi.moqu.marshall.ObjectMapperFactory;
+
+class OpenAPIMoquImporterTest {
+
+ private final MoquImporter sut = new OpenAPIMoquImporter();
+
+ @Test
+ @DisplayName("Should create a new definition from OpenAPI specification")
+ void shouldCreateANewDefinitionFromOpenAPISpecification() {
+ // act
+ String content = readContentFromFile("wiremock/one_example_in_the_same_path.yml");
+ Moqu moqu = sut.parse(content);
+
+ // assert
+ SoftAssertions.assertSoftly(softly -> {
+ softly.assertThat(moqu.getRequestResponsePairs()).isNotEmpty();
+ softly.assertThat(moqu.getRequestResponsePairs()).hasSize(1);
+ softly.assertThat(moqu.getRequestResponsePairs().get(0)).satisfies(requestResponsePair -> {
+ softly.assertThat(requestResponsePair.request().accept().name()).isEqualTo("Accept");
+ softly.assertThat(requestResponsePair.request().accept().value()).contains("application/json");
+ softly.assertThat(requestResponsePair.request().exampleName()).isEqualTo("john");
+ softly.assertThat(requestResponsePair.request().parameters()).hasSize(1);
+ softly.assertThat(requestResponsePair.request().parameters()).anySatisfy(parameters -> {
+ softly.assertThat(parameters.key()).isEqualTo("id");
+ softly.assertThat(parameters.value()).isEqualTo("1");
+ });
+ });
+ });
+ }
+
+ @Test
+ @DisplayName("Should throws exception when the OpenAPI is invalid")
+ void shouldThrowsExceptionWhenTheOpenAPIIsInvalid() {
+ // act, assert
+ Assertions.assertThrows(IllegalArgumentException.class, () -> {
+ sut.parse("""
+ openapi: 3.0.3
+ info:
+ version: 999-SNAPSHOT
+ """);
+ });
+ }
+
+ @Test
+ @DisplayName("Should handle OpenAPI with two path params")
+ void shouldHandleOpenAPIWithTwoPathParams() {
+
+ // act
+ String content = readContentFromFile("wiremock/two_examples_in_the_same_path.yml");
+ Moqu moqu = sut.parse(content);
+
+ // assert
+ SoftAssertions.assertSoftly(softly -> {
+ softly.assertThat(moqu.getRequestResponsePairs()).isNotEmpty();
+ softly.assertThat(moqu.getRequestResponsePairs()).hasSize(2);
+ softly.assertThat(moqu.getRequestResponsePairs()).allSatisfy(requestResponsePair -> {
+ softly.assertThat(requestResponsePair.request().accept().name()).isEqualTo("Accept");
+ softly.assertThat(requestResponsePair.request().accept().value()).contains("application/json");
+ });
+ softly.assertThat(moqu.getRequestResponsePairs()).anySatisfy(requestResponsePair -> {
+ softly.assertThat(requestResponsePair.request().exampleName()).isEqualTo("john");
+ softly.assertThat(requestResponsePair.request().parameters()).hasSize(1);
+ });
+ softly.assertThat(moqu.getRequestResponsePairs()).anySatisfy(requestResponsePair -> {
+ softly.assertThat(requestResponsePair.request().exampleName()).isEqualTo("mary");
+ softly.assertThat(requestResponsePair.request().parameters()).hasSize(1);
+ });
+ });
+ }
+
+ @Test
+ @DisplayName("Should generate a response from ref")
+ void shouldGenerateAResponseFromRef() {
+ String content = readContentFromFile("wiremock/response_from_ref.yml");
+
+ Moqu moqu = sut.parse(content);
+
+ // assert
+ SoftAssertions.assertSoftly(softly -> {
+ softly.assertThat(moqu.getRequestResponsePairs()).isNotEmpty();
+ softly.assertThat(moqu.getRequestResponsePairs()).hasSize(1);
+ softly.assertThat(moqu.getRequestResponsePairs().get(0)).satisfies(requestResponsePair -> {
+ softly.assertThat(requestResponsePair.request().accept().name()).isEqualTo("Accept");
+ softly.assertThat(requestResponsePair.request().accept().value()).contains("application/json");
+ softly.assertThat(requestResponsePair.request().exampleName()).isEqualTo("quarkus");
+ softly.assertThat(requestResponsePair.request().parameters()).hasSize(1);
+ softly.assertThat(requestResponsePair.request().parameters()).anySatisfy(parameters -> {
+ softly.assertThat(parameters.key()).isEqualTo("id");
+ softly.assertThat(parameters.value()).isEqualTo("1");
+ });
+ });
+ });
+ }
+
+ @Test
+ @DisplayName("Should generate a response from ref as array")
+ void shouldGenerateAResponseFromRefAsArray() {
+ String content = readContentFromFile("wiremock/response_from_ref_array.yml");
+ Moqu moqu = sut.parse(content);
+ SoftAssertions.assertSoftly(softly -> {
+ softly.assertThat(moqu.getRequestResponsePairs()).isNotEmpty();
+ softly.assertThat(moqu.getRequestResponsePairs()).hasSize(1);
+ softly.assertThat(moqu.getRequestResponsePairs().get(0)).satisfies(requestResponsePair -> {
+ Map map = ObjectMapperFactory.getInstance().readValue(
+ requestResponsePair.response()
+ .content(),
+ Map.class);
+ softly.assertThat((List>) map.get("versions"))
+ .hasSize(2);
+
+ softly.assertThat(map.get("supportsJava")).isEqualTo(true);
+
+ });
+ });
+ }
+
+ @Test
+ @DisplayName("Should generate a response from $ref and with no $ref")
+ void shouldGenerateAResponseFromRefAndNoRef() {
+ String content = readContentFromFile("wiremock/response_from_ref_and_noref.yml");
+ Moqu moqu = sut.parse(content);
+ SoftAssertions.assertSoftly(softly -> {
+ softly.assertThat(moqu.getRequestResponsePairs()).isNotEmpty();
+ softly.assertThat(moqu.getRequestResponsePairs()).hasSize(2);
+ softly.assertThat(moqu.getRequestResponsePairs()).anySatisfy(requestResponsePair -> {
+ Map map = ObjectMapperFactory.getInstance().readValue(
+ requestResponsePair.response()
+ .content(),
+ Map.class);
+ softly.assertThat((List) map.get("versions"))
+ .hasSize(2);
+ });
+
+ softly.assertThat(moqu.getRequestResponsePairs()).anySatisfy(requestResponsePair -> {
+ Map map = ObjectMapperFactory.getInstance().readValue(
+ requestResponsePair.response()
+ .content(),
+ Map.class);
+ softly.assertThat((List) map.get("versions"))
+ .hasSize(1);
+ });
+ });
+ }
+
+ @Test
+ @DisplayName("Should generate a full OpenAPI specification")
+ void shouldGenerateAFullResponse() {
+ String content = readContentFromFile("wiremock/full.yml");
+ Moqu moqu = sut.parse(content);
+ SoftAssertions.assertSoftly(softly -> {
+ softly.assertThat(moqu.getRequestResponsePairs()).isNotEmpty();
+ softly.assertThat(moqu.getRequestResponsePairs()).hasSize(1);
+ softly.assertThat(moqu.getRequestResponsePairs().get(0)).satisfies(requestResponsePair -> {
+ Map map = ObjectMapperFactory.getInstance().readValue(
+ requestResponsePair.response()
+ .content(),
+ Map.class);
+ softly.assertThat((List>) map.get("versions"))
+ .hasSize(2);
+
+ softly.assertThat(map.get("supportsJava")).isEqualTo(true);
+
+ softly.assertThat(map.get("contributors")).isEqualTo(1000);
+
+ softly.assertThat(((Map) map.get("rules")).get("hello")).isEqualTo("world");
+ });
+ });
+ }
+}
diff --git a/moqu/core/src/test/java/io/quarkiverse/openapi/moqu/TestUtils.java b/moqu/core/src/test/java/io/quarkiverse/openapi/moqu/TestUtils.java
new file mode 100644
index 000000000..2f8b1b571
--- /dev/null
+++ b/moqu/core/src/test/java/io/quarkiverse/openapi/moqu/TestUtils.java
@@ -0,0 +1,20 @@
+package io.quarkiverse.openapi.moqu;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+public class TestUtils {
+
+ public static String readContentFromFile(String resourcePath) {
+ URL url = Thread.currentThread().getContextClassLoader().getResource((resourcePath));
+ assert url != null;
+ try {
+ return Files.readString(Path.of(url.toURI()));
+ } catch (IOException | URISyntaxException e) {
+ return null;
+ }
+ }
+}
diff --git a/moqu/core/src/test/java/io/quarkiverse/openapi/moqu/wiremock/mapper/WiremockMapperTest.java b/moqu/core/src/test/java/io/quarkiverse/openapi/moqu/wiremock/mapper/WiremockMapperTest.java
new file mode 100644
index 000000000..9a8611bab
--- /dev/null
+++ b/moqu/core/src/test/java/io/quarkiverse/openapi/moqu/wiremock/mapper/WiremockMapperTest.java
@@ -0,0 +1,65 @@
+package io.quarkiverse.openapi.moqu.wiremock.mapper;
+
+import static io.quarkiverse.openapi.moqu.TestUtils.readContentFromFile;
+
+import java.util.List;
+
+import org.assertj.core.api.SoftAssertions;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import io.quarkiverse.openapi.moqu.Moqu;
+import io.quarkiverse.openapi.moqu.OpenAPIMoquImporter;
+import io.quarkiverse.openapi.moqu.wiremock.model.WiremockMapping;
+
+class WiremockMapperTest {
+
+ private final OpenAPIMoquImporter importer = new OpenAPIMoquImporter();
+ private final WiremockMapper sut = new WiremockMapper();
+
+ @Test
+ @DisplayName("Should map one Wiremock definition")
+ void shouldMapOneWiremockDefinition() {
+ String content = readContentFromFile("wiremock/mapper/should_map_one_wiremock_definition.yml");
+
+ Moqu moqu = importer.parse(content);
+
+ List definitions = sut.map(moqu);
+
+ SoftAssertions.assertSoftly(softly -> {
+ softly.assertThat(definitions).isNotEmpty();
+ softly.assertThat(definitions).hasSize(1);
+ softly.assertThat(definitions).anySatisfy(definition -> {
+ softly.assertThat(definition.request().method()).isEqualTo("GET");
+ softly.assertThat(definition.request().url()).isEqualTo("/users/1");
+ softly.assertThat(definition.response().body()).isEqualTo("{\"id\": 1, \"name\": \"John Doe\"}");
+ });
+ });
+ }
+
+ @Test
+ @DisplayName("Should map two Wiremock definitions")
+ void shouldMapTwoWiremockDefinitions() {
+ String content = readContentFromFile("wiremock/mapper/should_map_two_wiremock_definition.yml");
+
+ Moqu mock = importer.parse(content);
+
+ List definitions = sut.map(mock);
+
+ SoftAssertions.assertSoftly(softly -> {
+ softly.assertThat(definitions).isNotEmpty();
+ softly.assertThat(definitions).hasSize(2);
+ softly.assertThat(definitions).anySatisfy(definition -> {
+ softly.assertThat(definition.request().method()).isEqualTo("GET");
+ softly.assertThat(definition.request().url()).isEqualTo("/users/1");
+ softly.assertThat(definition.response().body()).isEqualTo("{\"id\": 1, \"name\": \"John Doe\"}");
+ });
+
+ softly.assertThat(definitions).anySatisfy(definition -> {
+ softly.assertThat(definition.request().method()).isEqualTo("GET");
+ softly.assertThat(definition.request().url()).isEqualTo("/users/2");
+ softly.assertThat(definition.response().body()).isEqualTo("{\"id\": 2, \"name\": \"Mary Doe\"}");
+ });
+ });
+ }
+}
diff --git a/moqu/core/src/test/java/io/quarkiverse/openapi/moqu/wiremock/mapper/WiremockPathParamTest.java b/moqu/core/src/test/java/io/quarkiverse/openapi/moqu/wiremock/mapper/WiremockPathParamTest.java
new file mode 100644
index 000000000..abff5f59a
--- /dev/null
+++ b/moqu/core/src/test/java/io/quarkiverse/openapi/moqu/wiremock/mapper/WiremockPathParamTest.java
@@ -0,0 +1,164 @@
+package io.quarkiverse.openapi.moqu.wiremock.mapper;
+
+import java.util.List;
+
+import org.assertj.core.api.Assertions;
+import org.assertj.core.api.SoftAssertions;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import io.quarkiverse.openapi.moqu.Moqu;
+import io.quarkiverse.openapi.moqu.OpenAPIMoquImporter;
+import io.quarkiverse.openapi.moqu.TestUtils;
+import io.quarkiverse.openapi.moqu.wiremock.model.WiremockMapping;
+
+public class WiremockPathParamTest {
+
+ @Test
+ @DisplayName("Should convert a OpenAPI with a single path param correctly")
+ void shouldMapOneWiremockDefinition() {
+
+ String content = TestUtils.readContentFromFile("wiremock/path_param_one_path_param.yml");
+ if (content == null) {
+ Assertions.fail("Was not possible to read the file!");
+ }
+
+ OpenAPIMoquImporter importer = new OpenAPIMoquImporter();
+
+ Moqu mock = importer.parse(content);
+
+ WiremockMapper wiremockMapper = new WiremockMapper();
+
+ List definitions = wiremockMapper.map(mock);
+
+ SoftAssertions.assertSoftly(softly -> {
+ softly.assertThat(definitions).hasSize(1);
+ WiremockMapping definition = definitions.get(0);
+
+ softly.assertThat(definition).satisfies(wiremockDefinition -> {
+ // request
+ softly.assertThat(wiremockDefinition.request().url()).isEqualTo("/users/1");
+ softly.assertThat(wiremockDefinition.request().method()).isEqualTo("GET");
+ // response
+ softly.assertThat(wiremockDefinition.response().status()).isEqualTo(200);
+ softly.assertThat(wiremockDefinition.response().body()).isEqualTo("{\"name\": \"Quarkus\"}");
+ });
+ });
+ }
+
+ @Test
+ @DisplayName("Should convert with a two OpenAPI#paths each one with one path param")
+ void shouldMapTwoWiremockDefinitions() {
+
+ String content = TestUtils.readContentFromFile("wiremock/path_param_two_params_but_different_path.yml");
+ if (content == null) {
+ Assertions.fail("Was not possible to read the file!");
+ }
+
+ OpenAPIMoquImporter importer = new OpenAPIMoquImporter();
+
+ Moqu mock = importer.parse(content);
+
+ WiremockMapper wiremockMapper = new WiremockMapper();
+
+ List definitions = wiremockMapper.map(mock);
+
+ SoftAssertions.assertSoftly(softly -> {
+ softly.assertThat(definitions).hasSize(2);
+
+ softly.assertThat(definitions).anySatisfy(wiremockDefinition -> {
+ // request
+ softly.assertThat(wiremockDefinition.request().url()).isEqualTo("/users/1");
+ softly.assertThat(wiremockDefinition.request().method()).isEqualTo("GET");
+ // response
+ softly.assertThat(wiremockDefinition.response().status()).isEqualTo(200);
+ softly.assertThat(wiremockDefinition.response().body()).isEqualTo("{\"name\": \"John Doe\"}");
+ });
+
+ softly.assertThat(definitions).anySatisfy(wiremockDefinition -> {
+ // request
+ softly.assertThat(wiremockDefinition.request().url()).isEqualTo("/frameworks/quarkus");
+ softly.assertThat(wiremockDefinition.request().method()).isEqualTo("GET");
+ // response
+ softly.assertThat(wiremockDefinition.response().status()).isEqualTo(200);
+ softly.assertThat(wiremockDefinition.response().body())
+ .isEqualTo("{\"description\": \"Quarkus, build time augmentation toolkit\"}");
+ });
+ });
+ }
+
+ @Test
+ @DisplayName("Should convert with a combination of path param")
+ void shouldConvertWithACombinationOfPathParam() {
+
+ String content = TestUtils.readContentFromFile("wiremock/path_param_two_path_params_combination.yml");
+ if (content == null) {
+ Assertions.fail("Was not possible to read the file!");
+ }
+
+ OpenAPIMoquImporter importer = new OpenAPIMoquImporter();
+
+ Moqu mock = importer.parse(content);
+
+ WiremockMapper wiremockMapper = new WiremockMapper();
+
+ List definitions = wiremockMapper.map(mock);
+
+ SoftAssertions.assertSoftly(softly -> {
+ softly.assertThat(definitions).hasSize(2);
+
+ softly.assertThat(definitions).anySatisfy(wiremockDefinition -> {
+ // request
+ softly.assertThat(wiremockDefinition.request().url()).isEqualTo("/users/1/books/80");
+ softly.assertThat(wiremockDefinition.request().method()).isEqualTo("GET");
+ // response
+ softly.assertThat(wiremockDefinition.response().status()).isEqualTo(200);
+ softly.assertThat(wiremockDefinition.response().body())
+ .isEqualTo("{\"name\": \"Book for John\", \"chapters\": 8}");
+ });
+
+ softly.assertThat(definitions).anySatisfy(wiremockDefinition -> {
+ // request
+ softly.assertThat(wiremockDefinition.request().url()).isEqualTo("/users/2/books/70");
+ softly.assertThat(wiremockDefinition.request().method()).isEqualTo("GET");
+ // response
+ softly.assertThat(wiremockDefinition.response().status()).isEqualTo(200);
+ softly.assertThat(wiremockDefinition.response().body())
+ .isEqualTo("{\"name\": \"Book for Mary\", \"chapters\": 10}");
+ });
+ });
+ }
+
+ @Test
+ @DisplayName("Should convert with a combination but only one with example")
+ void shouldConvertPathParamCombinationOnlyOneWithExample() {
+
+ String content = TestUtils.readContentFromFile("wiremock/path_param_two_path_params_only_one_with_example.yml");
+ if (content == null) {
+ Assertions.fail("Was not possible to read the file!");
+ }
+
+ OpenAPIMoquImporter importer = new OpenAPIMoquImporter();
+
+ Moqu mock = importer.parse(content);
+
+ WiremockMapper wiremockMapper = new WiremockMapper();
+
+ List definitions = wiremockMapper.map(mock);
+
+ SoftAssertions.assertSoftly(softly -> {
+ softly.assertThat(definitions).hasSize(1);
+
+ softly.assertThat(definitions).anySatisfy(wiremockDefinition -> {
+ // request
+ softly.assertThat(wiremockDefinition.request().url()).isEqualTo("/users/1/books/{bookId}");
+ softly.assertThat(wiremockDefinition.request().method()).isEqualTo("GET");
+ // response
+ softly.assertThat(wiremockDefinition.response().status()).isEqualTo(200);
+ softly.assertThat(wiremockDefinition.response().body())
+ .isEqualTo("{\"name\": \"Book for John\", \"chapters\": 8}");
+ });
+ });
+ }
+
+}
diff --git a/moqu/core/src/test/resources/wiremock/full.yml b/moqu/core/src/test/resources/wiremock/full.yml
new file mode 100644
index 000000000..d91f48923
--- /dev/null
+++ b/moqu/core/src/test/resources/wiremock/full.yml
@@ -0,0 +1,45 @@
+openapi: 3.0.3
+servers:
+ - url: http://localhost:8888
+info:
+ version: 999-SNAPSHOT
+ title: Method GET one path param
+paths:
+ "/frameworks/{id}":
+ get:
+ parameters:
+ - name: id
+ in: path
+ examples:
+ quarkus:
+ value: 1
+ responses:
+ 200:
+ content:
+ "application/json":
+ examples:
+ quarkus:
+ $ref: "#/components/schemas/Framework"
+ description: Ok
+components:
+ schemas:
+ Framework:
+ type: object
+ properties:
+ name:
+ type: string
+ example: "Quarkus"
+ versions:
+ type: array
+ example: ["999-SNAPSHOT", "3.15.1"]
+ supportsJava:
+ type: boolean
+ example: true
+ contributors:
+ type: integer
+ example: 1000
+ rules:
+ type: object
+ example:
+ hello: world
+
diff --git a/moqu/core/src/test/resources/wiremock/mapper/should_map_one_wiremock_definition.yml b/moqu/core/src/test/resources/wiremock/mapper/should_map_one_wiremock_definition.yml
new file mode 100644
index 000000000..92ea0200c
--- /dev/null
+++ b/moqu/core/src/test/resources/wiremock/mapper/should_map_one_wiremock_definition.yml
@@ -0,0 +1,28 @@
+openapi: 3.0.3
+info:
+ title: "Users API"
+ version: 1.0.0-alpha
+servers:
+ - url: http://localhost:8888
+paths:
+ /users/{userId}:
+ get:
+ description: Get user by ID
+ parameters:
+ - name: userId
+ in: path
+ required: true
+ schema:
+ type: number
+ examples:
+ john:
+ value: 1
+ responses:
+ "200":
+ description: Ok
+ content:
+ "application/json":
+ examples:
+ john:
+ value:
+ '{"id": 1, "name": "John Doe"}'
\ No newline at end of file
diff --git a/moqu/core/src/test/resources/wiremock/mapper/should_map_two_wiremock_definition.yml b/moqu/core/src/test/resources/wiremock/mapper/should_map_two_wiremock_definition.yml
new file mode 100644
index 000000000..2dabf118f
--- /dev/null
+++ b/moqu/core/src/test/resources/wiremock/mapper/should_map_two_wiremock_definition.yml
@@ -0,0 +1,33 @@
+openapi: 3.0.3
+info:
+ title: "Users API"
+ version: 1.0.0-alpha
+servers:
+ - url: http://localhost:8888
+paths:
+ /users/{userId}:
+ get:
+ description: Get user by ID
+ parameters:
+ - name: userId
+ in: path
+ required: true
+ schema:
+ type: number
+ examples:
+ john:
+ value: 1
+ mary:
+ value: 2
+ responses:
+ "200":
+ description: Ok
+ content:
+ "application/json":
+ examples:
+ john:
+ value:
+ '{"id": 1, "name": "John Doe"}'
+ mary:
+ value:
+ '{"id": 2, "name": "Mary Doe"}'
\ No newline at end of file
diff --git a/moqu/core/src/test/resources/wiremock/one_example_in_the_same_path.yml b/moqu/core/src/test/resources/wiremock/one_example_in_the_same_path.yml
new file mode 100644
index 000000000..eac9598b5
--- /dev/null
+++ b/moqu/core/src/test/resources/wiremock/one_example_in_the_same_path.yml
@@ -0,0 +1,28 @@
+openapi: 3.0.3
+info:
+ title: "Users API"
+ version: 1.0.0-alpha
+servers:
+ - url: http://localhost:8888
+paths:
+ /users/{id}:
+ get:
+ description: Get user by ID
+ parameters:
+ - name: id
+ in: path
+ required: true
+ schema:
+ type: number
+ examples:
+ john:
+ value: 1
+ responses:
+ "200":
+ description: Ok
+ content:
+ "application/json":
+ examples:
+ john:
+ value:
+ '{"id": 1, "name": "John Doe"}'
\ No newline at end of file
diff --git a/moqu/core/src/test/resources/wiremock/path_param_one_path_param.yml b/moqu/core/src/test/resources/wiremock/path_param_one_path_param.yml
new file mode 100644
index 000000000..a87227d57
--- /dev/null
+++ b/moqu/core/src/test/resources/wiremock/path_param_one_path_param.yml
@@ -0,0 +1,23 @@
+openapi: 3.0.3
+servers:
+ - url: http://localhost:8888
+info:
+ version: 999-SNAPSHOT
+ title: Method GET one path param
+paths:
+ "/users/{userId}":
+ get:
+ parameters:
+ - name: userId
+ in: path
+ examples:
+ quarkus:
+ value: 1
+ responses:
+ 200:
+ content:
+ "application/json":
+ examples:
+ quarkus:
+ value: '{"name": "Quarkus"}'
+ description: Ok
diff --git a/moqu/core/src/test/resources/wiremock/path_param_two_params_but_different_path.yml b/moqu/core/src/test/resources/wiremock/path_param_two_params_but_different_path.yml
new file mode 100644
index 000000000..26eb2dba8
--- /dev/null
+++ b/moqu/core/src/test/resources/wiremock/path_param_two_params_but_different_path.yml
@@ -0,0 +1,39 @@
+openapi: 3.0.3
+servers:
+ - url: http://localhost:8888
+info:
+ version: 999-SNAPSHOT
+ title: Method GET one path param
+paths:
+ "/users/{userId}":
+ get:
+ parameters:
+ - name: userId
+ in: path
+ examples:
+ john:
+ value: 1
+ responses:
+ 200:
+ content:
+ "application/json":
+ examples:
+ john:
+ value: '{"name": "John Doe"}'
+ description: Ok
+ "/frameworks/{name}":
+ get:
+ parameters:
+ - name: name
+ in: path
+ examples:
+ quarkus:
+ value: quarkus
+ responses:
+ 200:
+ content:
+ "application/json":
+ examples:
+ quarkus:
+ value: '{"description": "Quarkus, build time augmentation toolkit"}'
+ description: Ok
diff --git a/moqu/core/src/test/resources/wiremock/path_param_two_path_params_combination.yml b/moqu/core/src/test/resources/wiremock/path_param_two_path_params_combination.yml
new file mode 100644
index 000000000..140eec479
--- /dev/null
+++ b/moqu/core/src/test/resources/wiremock/path_param_two_path_params_combination.yml
@@ -0,0 +1,35 @@
+openapi: 3.0.3
+servers:
+ - url: http://localhost:8888
+info:
+ version: 999-SNAPSHOT
+ title: Method GET one path param
+paths:
+ "/users/{userId}/books/{bookId}":
+ get:
+ parameters:
+ - name: userId
+ in: path
+ examples:
+ john:
+ value: 1
+ mary:
+ value: 2
+ - name: bookId
+ in: path
+ examples:
+ john:
+ value: 80
+ mary:
+ value: 70
+
+ responses:
+ 200:
+ content:
+ "application/json":
+ examples:
+ john:
+ value: '{"name": "Book for John", "chapters": 8}'
+ mary:
+ value: '{"name": "Book for Mary", "chapters": 10}'
+ description: Ok
diff --git a/moqu/core/src/test/resources/wiremock/path_param_two_path_params_only_one_with_example.yml b/moqu/core/src/test/resources/wiremock/path_param_two_path_params_only_one_with_example.yml
new file mode 100644
index 000000000..3b1f159a0
--- /dev/null
+++ b/moqu/core/src/test/resources/wiremock/path_param_two_path_params_only_one_with_example.yml
@@ -0,0 +1,25 @@
+openapi: 3.0.3
+servers:
+ - url: http://localhost:8888
+info:
+ version: 999-SNAPSHOT
+ title: Method GET one path param
+paths:
+ "/users/{userId}/books/{bookId}":
+ get:
+ parameters:
+ - name: userId
+ in: path
+ examples:
+ john:
+ value: 1
+ - name: bookId
+ in: path
+ responses:
+ 200:
+ content:
+ "application/json":
+ examples:
+ john:
+ value: '{"name": "Book for John", "chapters": 8}'
+ description: Ok
\ No newline at end of file
diff --git a/moqu/core/src/test/resources/wiremock/response_from_ref.yml b/moqu/core/src/test/resources/wiremock/response_from_ref.yml
new file mode 100644
index 000000000..4e62b37fb
--- /dev/null
+++ b/moqu/core/src/test/resources/wiremock/response_from_ref.yml
@@ -0,0 +1,31 @@
+openapi: 3.0.3
+servers:
+ - url: http://localhost:8888
+info:
+ version: 999-SNAPSHOT
+ title: Method GET one path param
+paths:
+ "/frameworks/{id}":
+ get:
+ parameters:
+ - name: id
+ in: path
+ examples:
+ quarkus:
+ value: 1
+ responses:
+ 200:
+ content:
+ "application/json":
+ examples:
+ quarkus:
+ $ref: "#/components/schemas/Framework"
+ description: Ok
+components:
+ schemas:
+ Framework:
+ type: object
+ properties:
+ name:
+ type: string
+ example: "Quarkus"
\ No newline at end of file
diff --git a/moqu/core/src/test/resources/wiremock/response_from_ref_and_noref.yml b/moqu/core/src/test/resources/wiremock/response_from_ref_and_noref.yml
new file mode 100644
index 000000000..b9537febc
--- /dev/null
+++ b/moqu/core/src/test/resources/wiremock/response_from_ref_and_noref.yml
@@ -0,0 +1,38 @@
+openapi: 3.0.3
+servers:
+ - url: http://localhost:8888
+info:
+ version: 999-SNAPSHOT
+ title: Method GET one path param
+paths:
+ "/frameworks/{id}":
+ get:
+ parameters:
+ - name: id
+ in: path
+ examples:
+ quarkus:
+ value: 1
+ vertx:
+ value: 2
+ responses:
+ 200:
+ content:
+ "application/json":
+ examples:
+ quarkus:
+ $ref: "#/components/schemas/Framework"
+ vertx:
+ value: '{ "name": "Vert.x", "versions": ["999-SNAPSHOT"]}'
+ description: Ok
+components:
+ schemas:
+ Framework:
+ type: object
+ properties:
+ name:
+ type: string
+ example: "Quarkus"
+ versions:
+ type: array
+ example: [ "999-SNAPSHOT", "3.15.1" ]
diff --git a/moqu/core/src/test/resources/wiremock/response_from_ref_array.yml b/moqu/core/src/test/resources/wiremock/response_from_ref_array.yml
new file mode 100644
index 000000000..8707012bd
--- /dev/null
+++ b/moqu/core/src/test/resources/wiremock/response_from_ref_array.yml
@@ -0,0 +1,37 @@
+openapi: 3.0.3
+servers:
+ - url: http://localhost:8888
+info:
+ version: 999-SNAPSHOT
+ title: Method GET one path param
+paths:
+ "/frameworks/{id}":
+ get:
+ parameters:
+ - name: id
+ in: path
+ examples:
+ quarkus:
+ value: 1
+ responses:
+ 200:
+ content:
+ "application/json":
+ examples:
+ quarkus:
+ $ref: "#/components/schemas/Framework"
+ description: Ok
+components:
+ schemas:
+ Framework:
+ type: object
+ properties:
+ name:
+ type: string
+ example: "Quarkus"
+ versions:
+ type: array
+ example: ["999-SNAPSHOT", "3.15.1"]
+ supportsJava:
+ type: boolean
+ example: true
diff --git a/moqu/core/src/test/resources/wiremock/two_examples_in_the_same_path.yml b/moqu/core/src/test/resources/wiremock/two_examples_in_the_same_path.yml
new file mode 100644
index 000000000..2dabf118f
--- /dev/null
+++ b/moqu/core/src/test/resources/wiremock/two_examples_in_the_same_path.yml
@@ -0,0 +1,33 @@
+openapi: 3.0.3
+info:
+ title: "Users API"
+ version: 1.0.0-alpha
+servers:
+ - url: http://localhost:8888
+paths:
+ /users/{userId}:
+ get:
+ description: Get user by ID
+ parameters:
+ - name: userId
+ in: path
+ required: true
+ schema:
+ type: number
+ examples:
+ john:
+ value: 1
+ mary:
+ value: 2
+ responses:
+ "200":
+ description: Ok
+ content:
+ "application/json":
+ examples:
+ john:
+ value:
+ '{"id": 1, "name": "John Doe"}'
+ mary:
+ value:
+ '{"id": 2, "name": "Mary Doe"}'
\ No newline at end of file
diff --git a/moqu/deployment/pom.xml b/moqu/deployment/pom.xml
new file mode 100644
index 000000000..0eb16fca6
--- /dev/null
+++ b/moqu/deployment/pom.xml
@@ -0,0 +1,66 @@
+
+
+
+ io.quarkiverse.openapi.generator
+ quarkus-openapi-generator-moqu-parent
+ 3.0.0-SNAPSHOT
+
+ 4.0.0
+
+ quarkus-openapi-generator-moqu-wiremock-deployment
+ Quarkus - Openapi Generator - Moqu - Wiremock - Deployment
+
+
+
+ io.quarkus
+ quarkus-core-deployment
+
+
+
+ io.quarkiverse.openapi.generator
+ quarkus-openapi-generator-moqu-wiremock
+ ${project.version}
+
+
+
+ io.quarkiverse.openapi.generator
+ quarkus-openapi-generator-moqu-core
+ ${project.version}
+
+
+
+ io.quarkus
+ quarkus-junit5-internal
+
+
+
+ io.quarkus
+ quarkus-vertx-http-deployment
+
+
+
+ io.rest-assured
+ rest-assured
+ test
+
+
+
+
+
+
+
+ maven-compiler-plugin
+
+
+
+ io.quarkus
+ quarkus-extension-processor
+ ${quarkus.version}
+
+
+
+
+
+
+
diff --git a/moqu/deployment/src/main/java/io/quarkiverse/openapi/generator/MoquProjectProcessor.java b/moqu/deployment/src/main/java/io/quarkiverse/openapi/generator/MoquProjectProcessor.java
new file mode 100644
index 000000000..94ed8ff79
--- /dev/null
+++ b/moqu/deployment/src/main/java/io/quarkiverse/openapi/generator/MoquProjectProcessor.java
@@ -0,0 +1,102 @@
+package io.quarkiverse.openapi.generator;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import org.jboss.logging.Logger;
+
+import io.quarkiverse.openapi.generator.items.MoquBuildItem;
+import io.quarkiverse.openapi.generator.items.MoquProjectBuildItem;
+import io.quarkiverse.openapi.generator.moqu.MoquConfig;
+import io.quarkiverse.openapi.moqu.Moqu;
+import io.quarkiverse.openapi.moqu.OpenAPIMoquImporter;
+import io.quarkus.deployment.IsDevelopment;
+import io.quarkus.deployment.annotations.BuildProducer;
+import io.quarkus.deployment.annotations.BuildStep;
+import io.quarkus.runtime.util.ClassPathUtils;
+
+public class MoquProjectProcessor {
+
+ private static final Logger LOGGER = Logger.getLogger(MoquProjectProcessor.class);
+
+ private static final Set SUPPORTED_EXTENSIONS = Set.of("yaml", "yml", "json");
+
+ @BuildStep
+ MoquProjectBuildItem generate(MoquConfig config) {
+ try {
+
+ HashMap filesMap = new HashMap<>();
+ ClassPathUtils.consumeAsPaths(config.resourceDir(), path -> {
+ try {
+ boolean directory = Files.isDirectory(path);
+ if (directory) {
+ try (Stream pathStream = Files.find(path, Integer.MAX_VALUE,
+ (p, a) -> Files.isRegularFile(p) && SUPPORTED_EXTENSIONS.contains(
+ getExtension(p.getFileName().toString())))) {
+
+ pathStream.forEach(p -> {
+ try {
+ String filename = p.getFileName().toString();
+
+ MoquProjectBuildItem.File moquFile = new MoquProjectBuildItem.File(
+ removeExtension(filename), getExtension(filename), Files.readString(p));
+
+ filesMap.put(filename, moquFile);
+
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ }
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ return new MoquProjectBuildItem(filesMap);
+
+ } catch (IOException e) {
+ LOGGER.error("Was not possible to scan Moqu project files.", e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ @BuildStep(onlyIf = { IsDevelopment.class })
+ void consume(Optional moquProject,
+ BuildProducer moquMocks) {
+
+ OpenAPIMoquImporter importer = new OpenAPIMoquImporter();
+ moquProject.ifPresent(project -> {
+ for (Map.Entry spec : project.specs().entrySet()) {
+
+ MoquProjectBuildItem.File moquFile = spec.getValue();
+
+ Moqu moqu = importer.parse(moquFile.content());
+
+ moquMocks.produce(new MoquBuildItem(
+ moquFile.filename(),
+ moquFile.extension(),
+ moqu));
+ }
+ });
+ }
+
+ public static String getExtension(String path) {
+ Objects.requireNonNull(path, "path is required");
+ final int i = path.lastIndexOf(".");
+ return i > 0 ? path.substring(i + 1) : null;
+ }
+
+ public static String removeExtension(String path) {
+ Objects.requireNonNull(path, "path is required");
+ final int i = path.lastIndexOf(".");
+ return i > 0 ? path.substring(0, i) : path;
+ }
+}
diff --git a/moqu/deployment/src/main/java/io/quarkiverse/openapi/generator/MoquWiremockProcessor.java b/moqu/deployment/src/main/java/io/quarkiverse/openapi/generator/MoquWiremockProcessor.java
new file mode 100644
index 000000000..086f65f88
--- /dev/null
+++ b/moqu/deployment/src/main/java/io/quarkiverse/openapi/generator/MoquWiremockProcessor.java
@@ -0,0 +1,12 @@
+package io.quarkiverse.openapi.generator;
+
+import io.quarkus.deployment.annotations.BuildStep;
+import io.quarkus.deployment.builditem.FeatureBuildItem;
+
+public class MoquWiremockProcessor {
+
+ @BuildStep
+ FeatureBuildItem feature() {
+ return new FeatureBuildItem("moqu-wiremock");
+ }
+}
diff --git a/moqu/deployment/src/main/java/io/quarkiverse/openapi/generator/devui/MoquModel.java b/moqu/deployment/src/main/java/io/quarkiverse/openapi/generator/devui/MoquModel.java
new file mode 100644
index 000000000..38cc5c795
--- /dev/null
+++ b/moqu/deployment/src/main/java/io/quarkiverse/openapi/generator/devui/MoquModel.java
@@ -0,0 +1,5 @@
+package io.quarkiverse.openapi.generator.devui;
+
+public record MoquModel(String name, String link) {
+
+}
diff --git a/moqu/deployment/src/main/java/io/quarkiverse/openapi/generator/devui/MoquWiremockDevUIProcessor.java b/moqu/deployment/src/main/java/io/quarkiverse/openapi/generator/devui/MoquWiremockDevUIProcessor.java
new file mode 100644
index 000000000..0514701af
--- /dev/null
+++ b/moqu/deployment/src/main/java/io/quarkiverse/openapi/generator/devui/MoquWiremockDevUIProcessor.java
@@ -0,0 +1,81 @@
+package io.quarkiverse.openapi.generator.devui;
+
+import java.util.List;
+import java.util.Map;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import io.quarkiverse.openapi.generator.items.MoquBuildItem;
+import io.quarkiverse.openapi.generator.moqu.recorder.MoquRoutesRecorder;
+import io.quarkiverse.openapi.moqu.marshall.ObjectMapperFactory;
+import io.quarkiverse.openapi.moqu.wiremock.mapper.WiremockMapper;
+import io.quarkiverse.openapi.moqu.wiremock.model.WiremockMapping;
+import io.quarkus.deployment.IsDevelopment;
+import io.quarkus.deployment.annotations.BuildProducer;
+import io.quarkus.deployment.annotations.BuildStep;
+import io.quarkus.deployment.annotations.ExecutionTime;
+import io.quarkus.deployment.annotations.Record;
+import io.quarkus.devui.spi.page.CardPageBuildItem;
+import io.quarkus.devui.spi.page.Page;
+import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem;
+import io.quarkus.vertx.http.deployment.RouteBuildItem;
+
+public class MoquWiremockDevUIProcessor {
+
+ private static final String MAPPINGS_KEY = "mappings";
+ private static final String WIREMOCK_MAPPINGS_JSON = "/wiremock-mappings.json";
+
+ @BuildStep(onlyIf = IsDevelopment.class)
+ @Record(ExecutionTime.RUNTIME_INIT)
+ void generateWiremock(List mocks, NonApplicationRootPathBuildItem nonApplicationRootPath,
+ BuildProducer routes,
+ MoquRoutesRecorder recorder) {
+
+ WiremockMapper wiremockMapper = new WiremockMapper();
+ ObjectMapper objMapper = ObjectMapperFactory.getInstance();
+
+ for (MoquBuildItem mock : mocks) {
+ List wiremockMappings = wiremockMapper.map(mock.getMoqu());
+ try {
+ String json = objMapper.writeValueAsString(Map.of(
+ MAPPINGS_KEY, wiremockMappings));
+
+ String uri = mock.prefixUri(nonApplicationRootPath.resolvePath("moqu"))
+ .concat(WIREMOCK_MAPPINGS_JSON);
+
+ routes.produce(nonApplicationRootPath.routeBuilder()
+ .routeFunction(uri, recorder.handleFile(json))
+ .displayOnNotFoundPage()
+ .build());
+
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ @BuildStep(onlyIf = { IsDevelopment.class })
+ CardPageBuildItem cardPageBuildItem(
+ List moquMocks,
+ NonApplicationRootPathBuildItem nonApplicationRootPath) {
+ CardPageBuildItem cardPageBuildItem = new CardPageBuildItem();
+
+ List models = moquMocks.stream()
+ .map(m -> new MoquModel(m.getFullFilename(), m.prefixUri(
+ nonApplicationRootPath.resolvePath("moqu"))
+ .concat("/wiremock-mappings.json")))
+ .toList();
+
+ cardPageBuildItem.addBuildTimeData("mocks", models);
+
+ cardPageBuildItem.addPage(
+ Page.webComponentPageBuilder()
+ .title("Moqu Wiremock")
+ .icon("font-awesome-solid:server")
+ .componentLink("qwc-moqu.js")
+ .staticLabel(String.valueOf(moquMocks.size())));
+
+ return cardPageBuildItem;
+ }
+}
diff --git a/moqu/deployment/src/main/java/io/quarkiverse/openapi/generator/items/MoquBuildItem.java b/moqu/deployment/src/main/java/io/quarkiverse/openapi/generator/items/MoquBuildItem.java
new file mode 100644
index 000000000..a6285f734
--- /dev/null
+++ b/moqu/deployment/src/main/java/io/quarkiverse/openapi/generator/items/MoquBuildItem.java
@@ -0,0 +1,37 @@
+package io.quarkiverse.openapi.generator.items;
+
+import io.quarkiverse.openapi.moqu.Moqu;
+import io.quarkus.builder.item.MultiBuildItem;
+
+public final class MoquBuildItem extends MultiBuildItem {
+
+ private final String filename;
+ private final String extension;
+ private final Moqu moqu;
+
+ public MoquBuildItem(String filename, String extension, Moqu moqu) {
+ this.filename = filename;
+ this.extension = extension;
+ this.moqu = moqu;
+ }
+
+ public String getFilename() {
+ return filename;
+ }
+
+ public String getExtension() {
+ return extension;
+ }
+
+ public Moqu getMoqu() {
+ return moqu;
+ }
+
+ public String getFullFilename() {
+ return filename + "." + extension;
+ }
+
+ public String prefixUri(String basePath) {
+ return String.format("%s/%s/%s", basePath, extension, filename);
+ }
+}
diff --git a/moqu/deployment/src/main/java/io/quarkiverse/openapi/generator/items/MoquProjectBuildItem.java b/moqu/deployment/src/main/java/io/quarkiverse/openapi/generator/items/MoquProjectBuildItem.java
new file mode 100644
index 000000000..ea8de60a5
--- /dev/null
+++ b/moqu/deployment/src/main/java/io/quarkiverse/openapi/generator/items/MoquProjectBuildItem.java
@@ -0,0 +1,22 @@
+package io.quarkiverse.openapi.generator.items;
+
+import java.util.Collections;
+import java.util.Map;
+
+import io.quarkus.builder.item.SimpleBuildItem;
+
+public final class MoquProjectBuildItem extends SimpleBuildItem {
+
+ private final Map specs;
+
+ public MoquProjectBuildItem(Map specs) {
+ this.specs = specs;
+ }
+
+ public Map specs() {
+ return Collections.unmodifiableMap(specs);
+ }
+
+ public record File(String filename, String extension, String content) {
+ }
+}
diff --git a/moqu/deployment/src/main/resources/dev-ui/qwc-moqu.js b/moqu/deployment/src/main/resources/dev-ui/qwc-moqu.js
new file mode 100644
index 000000000..31d42cded
--- /dev/null
+++ b/moqu/deployment/src/main/resources/dev-ui/qwc-moqu.js
@@ -0,0 +1,96 @@
+import {LitElement, html, css} from 'lit';
+import {columnBodyRenderer} from '@vaadin/grid/lit.js';
+import {mocks} from 'build-time-data';
+import '@vaadin/grid';
+import '@vaadin/vertical-layout';
+import '@vaadin/icon';
+
+/**
+ * This component shows the Moqu mocks
+ */
+export class QwcMoqu extends LitElement {
+
+ static styles = css`
+ .arctable {
+ height: 100%;
+ padding-bottom: 10px;
+ }
+
+ .moqu-icon {
+ font-size: small;
+ color: var(--lumo-contrast-50pct);
+ cursor: pointer;
+ }
+ `;
+
+ static properties = {
+ _mocks: {state: true}
+ };
+
+ constructor() {
+ super();
+ this._mocks = mocks;
+ }
+
+ render() {
+ if (this._mocks) {
+ return this._renderMockList();
+ } else {
+ return html`No mocks found`;
+ }
+ }
+
+ _renderMockList() {
+ return html`
+
+
+
+
+
+
+
+
+ `;
+ }
+
+ _nameRenderer(mock) {
+ return html`
+
+ ${mock.name}
+
+ `;
+ }
+
+ _linkDownloadRenderer(mock) {
+ return html`
+
+
+
+
+
+ `;
+ }
+
+ _linkSeeRenderer(mock) {
+ return html`
+
+
+
+
+
+ `;
+ }
+
+
+}
+
+customElements.define('qwc-moqu', QwcMoqu);
diff --git a/moqu/deployment/src/test/java/io/quarkiverse/openapi/generator/MoquProjectProcessorTest.java b/moqu/deployment/src/test/java/io/quarkiverse/openapi/generator/MoquProjectProcessorTest.java
new file mode 100644
index 000000000..76347c237
--- /dev/null
+++ b/moqu/deployment/src/test/java/io/quarkiverse/openapi/generator/MoquProjectProcessorTest.java
@@ -0,0 +1,47 @@
+package io.quarkiverse.openapi.generator;
+
+import org.hamcrest.Matchers;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.test.QuarkusDevModeTest;
+import io.restassured.RestAssured;
+
+public class MoquProjectProcessorTest {
+
+ @RegisterExtension
+ static final QuarkusDevModeTest unitTest = new QuarkusDevModeTest()
+ .withApplicationRoot(javaArchive -> javaArchive
+ .addAsResource("api.yaml", "openapi/openapi.yaml")
+ .addAsResource("apiv2.json", "openapi/api.json"));
+
+ @Test
+ void testModeAsSee() {
+ RestAssured.given()
+ .when().get("/q/moqu/yaml/openapi/wiremock-mappings.json?mode=see")
+ .then()
+ .statusCode(200)
+ .body(Matchers.containsString("Alice"))
+ .log().ifError();
+ }
+
+ @Test
+ void testModeAsDownload() {
+ RestAssured.given()
+ .when().get("/q/moqu/yaml/openapi/wiremock-mappings.json")
+ .then()
+ .statusCode(200)
+ .body(Matchers.containsString("Alice"))
+ .log().ifError();
+ }
+
+ @Test
+ void testModeAsDownloadUsingJson() {
+ RestAssured.given()
+ .when().get("/q/moqu/json/api/wiremock-mappings.json")
+ .then()
+ .statusCode(200)
+ .body(Matchers.containsString("Alice"))
+ .log().ifError();
+ }
+}
diff --git a/moqu/deployment/src/test/resources/api.yaml b/moqu/deployment/src/test/resources/api.yaml
new file mode 100644
index 000000000..802ebe7e6
--- /dev/null
+++ b/moqu/deployment/src/test/resources/api.yaml
@@ -0,0 +1,35 @@
+openapi: 3.0.3
+servers:
+ - url: http://localhost:8888
+info:
+ version: 999-SNAPSHOT
+ title: Method GET one path param
+paths:
+ "/users/{id}":
+ get:
+ parameters:
+ - name: id
+ in: path
+ examples:
+ alice:
+ value: 1
+ responses:
+ 200:
+ content:
+ "application/json":
+ examples:
+ quarkus:
+ $ref: "#/components/schemas/User"
+ description: Ok
+components:
+ schemas:
+ User:
+ type: object
+ properties:
+ name:
+ type: string
+ example: "Alice"
+ age:
+ type: number
+ example: 80
+
diff --git a/moqu/deployment/src/test/resources/apiv2.json b/moqu/deployment/src/test/resources/apiv2.json
new file mode 100644
index 000000000..4bbe37c02
--- /dev/null
+++ b/moqu/deployment/src/test/resources/apiv2.json
@@ -0,0 +1,60 @@
+{
+ "openapi": "3.0.3",
+ "servers": [
+ {
+ "url": "http://localhost:8888"
+ }
+ ],
+ "info": {
+ "version": "999-SNAPSHOT",
+ "title": "Method GET one path param"
+ },
+ "paths": {
+ "/users/{id}": {
+ "get": {
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "examples": {
+ "alice": {
+ "value": 1
+ }
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Ok",
+ "content": {
+ "application/json": {
+ "examples": {
+ "quarkus": {
+ "$ref": "#/components/schemas/User"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "User": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "example": "Alice"
+ },
+ "age": {
+ "type": "number",
+ "example": 80
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/moqu/pom.xml b/moqu/pom.xml
new file mode 100644
index 000000000..79569a72b
--- /dev/null
+++ b/moqu/pom.xml
@@ -0,0 +1,19 @@
+
+
+
+ io.quarkiverse.openapi.generator
+ quarkus-openapi-generator-parent
+ 3.0.0-SNAPSHOT
+ ../pom.xml
+
+ 4.0.0
+ pom
+ quarkus-openapi-generator-moqu-parent
+ Quarkus - Openapi Generator - Moqu - Parent
+
+
+ core
+ deployment
+ runtime
+
+
diff --git a/moqu/runtime/pom.xml b/moqu/runtime/pom.xml
new file mode 100644
index 000000000..404ab0227
--- /dev/null
+++ b/moqu/runtime/pom.xml
@@ -0,0 +1,77 @@
+
+
+
+
+ io.quarkiverse.openapi.generator
+ quarkus-openapi-generator-moqu-parent
+ 3.0.0-SNAPSHOT
+
+ 4.0.0
+
+ quarkus-openapi-generator-moqu-wiremock
+ Quarkus - Openapi Generator - Moqu - Wiremock
+
+
+
+ io.quarkiverse.openapi.generator
+ quarkus-openapi-generator-moqu-core
+ ${project.version}
+
+
+
+ io.quarkus
+ quarkus-core
+
+
+
+ io.quarkus
+ quarkus-vertx-http
+
+
+
+ io.quarkus
+ quarkus-junit5
+ test
+
+
+
+ io.rest-assured
+ rest-assured
+ test
+
+
+
+
+
+ io.quarkus
+ quarkus-extension-maven-plugin
+ ${quarkus.version}
+
+
+ compile
+
+ extension-descriptor
+
+
+ ${project.groupId}:${project.artifactId}-deployment:${project.version}
+
+ true
+
+
+
+
+
+ maven-compiler-plugin
+
+
+
+ io.quarkus
+ quarkus-extension-processor
+ ${quarkus.version}
+
+
+
+
+
+
+
diff --git a/moqu/runtime/src/main/java/io/quarkiverse/openapi/generator/moqu/MoquConfig.java b/moqu/runtime/src/main/java/io/quarkiverse/openapi/generator/moqu/MoquConfig.java
new file mode 100644
index 000000000..ee9912ee8
--- /dev/null
+++ b/moqu/runtime/src/main/java/io/quarkiverse/openapi/generator/moqu/MoquConfig.java
@@ -0,0 +1,19 @@
+package io.quarkiverse.openapi.generator.moqu;
+
+import io.quarkus.runtime.annotations.ConfigPhase;
+import io.quarkus.runtime.annotations.ConfigRoot;
+import io.smallrye.config.ConfigMapping;
+import io.smallrye.config.WithDefault;
+
+@ConfigMapping(prefix = "quarkus.openapi-generator.moqu")
+@ConfigRoot(phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED)
+public interface MoquConfig {
+
+ String DEFAULT_RESOURCE_DIR = "openapi";
+
+ /**
+ * Path to the Moqu (relative to the project).
+ */
+ @WithDefault(DEFAULT_RESOURCE_DIR)
+ String resourceDir();
+}
diff --git a/moqu/runtime/src/main/java/io/quarkiverse/openapi/generator/moqu/recorder/MoquRoutesRecorder.java b/moqu/runtime/src/main/java/io/quarkiverse/openapi/generator/moqu/recorder/MoquRoutesRecorder.java
new file mode 100644
index 000000000..86d211c01
--- /dev/null
+++ b/moqu/runtime/src/main/java/io/quarkiverse/openapi/generator/moqu/recorder/MoquRoutesRecorder.java
@@ -0,0 +1,47 @@
+package io.quarkiverse.openapi.generator.moqu.recorder;
+
+import java.util.function.Consumer;
+
+import io.quarkus.runtime.annotations.Recorder;
+import io.vertx.core.Handler;
+import io.vertx.core.buffer.Buffer;
+import io.vertx.core.http.HttpMethod;
+import io.vertx.core.http.HttpServerRequest;
+import io.vertx.core.http.HttpServerResponse;
+import io.vertx.ext.web.Route;
+import io.vertx.ext.web.RoutingContext;
+
+@Recorder
+public class MoquRoutesRecorder {
+
+ public Consumer handleFile(String content) {
+ return new Consumer() {
+ @Override
+ public void accept(Route route) {
+ route.method(HttpMethod.GET);
+ route.handler(new Handler() {
+ @Override
+ public void handle(RoutingContext routingContext) {
+ HttpServerResponse response = routingContext.response();
+ HttpServerRequest request = routingContext.request();
+
+ String mode = request.getParam("mode");
+ if (mode != null && mode.equalsIgnoreCase("see")) {
+ response.putHeader("Content-Type", "application/json; charset=utf-8");
+ response.end(Buffer.buffer(content));
+ } else {
+ setForDownloading(response, content);
+ }
+ }
+
+ });
+ }
+ };
+ }
+
+ private void setForDownloading(HttpServerResponse response, String content) {
+ response.putHeader("Content-Type", "application/octet-stream");
+ response.putHeader("Content-Disposition", "attachment; filename=wiremock-mappings.json");
+ response.end(Buffer.buffer(content));
+ }
+}
diff --git a/moqu/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/moqu/runtime/src/main/resources/META-INF/quarkus-extension.yaml
new file mode 100644
index 000000000..c9383283a
--- /dev/null
+++ b/moqu/runtime/src/main/resources/META-INF/quarkus-extension.yaml
@@ -0,0 +1,11 @@
+name: "OpenAPI Generator - Moqu - Wiremock Generator"
+artifact: ${project.groupId}:${project.artifactId}:${project.version}
+description: The OpenAPI Generator Moqu Wiremock extension converts an OpenAPI specification into a Wiremock definition.
+metadata:
+ keywords:
+ - "openapi"
+ - "openapi-generator"
+ - "wiremock"
+ categories:
+ - "web"
+ status: "preview"
diff --git a/pom.xml b/pom.xml
index 391eecb3a..3028925ad 100644
--- a/pom.xml
+++ b/pom.xml
@@ -14,6 +14,8 @@
client
server
+ docs
+ moqu
:git:git@github.com:quarkiverse/quarkus-openapi-generator.git
@@ -32,6 +34,8 @@
3.27.3
4.1.2
3.12.1
+ 2.35.2
+ 2.1.22