diff --git a/multiapi-engine/pom.xml b/multiapi-engine/pom.xml
index 7b691435..ec41b1ee 100644
--- a/multiapi-engine/pom.xml
+++ b/multiapi-engine/pom.xml
@@ -4,7 +4,7 @@
com.sngular
multiapi-engine
- 6.4.1
+ 6.4.2
jar
diff --git a/multiapi-engine/src/main/java/com/sngular/api/generator/plugin/common/tools/ApiTool.java b/multiapi-engine/src/main/java/com/sngular/api/generator/plugin/common/tools/ApiTool.java
index f9aa1508..2f260e18 100644
--- a/multiapi-engine/src/main/java/com/sngular/api/generator/plugin/common/tools/ApiTool.java
+++ b/multiapi-engine/src/main/java/com/sngular/api/generator/plugin/common/tools/ApiTool.java
@@ -262,7 +262,10 @@ public static boolean hasName(JsonNode message) {
}
public static boolean hasRef(final JsonNode schema) {
- return (hasNode(schema, "$ref") || schema.fieldNames().hasNext()) && schema.fieldNames().next().equals("$ref");
+ // OpenAPI 3.1 / JSON Schema 2020-12 allow sibling keywords next to `$ref`
+ // (e.g. `description`), and their ordering is not significant. A node is a
+ // reference whenever it declares a top-level `$ref`, regardless of position.
+ return hasNode(schema, "$ref");
}
public static boolean hasProperties(final JsonNode schema) {
diff --git a/multiapi-engine/src/test/java/com/sngular/api/generator/plugin/openapi/OpenApiGeneratorFixtures.java b/multiapi-engine/src/test/java/com/sngular/api/generator/plugin/openapi/OpenApiGeneratorFixtures.java
index 7748c294..1dc6859b 100644
--- a/multiapi-engine/src/test/java/com/sngular/api/generator/plugin/openapi/OpenApiGeneratorFixtures.java
+++ b/multiapi-engine/src/test/java/com/sngular/api/generator/plugin/openapi/OpenApiGeneratorFixtures.java
@@ -175,6 +175,13 @@ public final class OpenApiGeneratorFixtures {
.clientPackage("com.sngular.multifileplugin.openapi31types.client")
.modelNameSuffix("DTO").build());
+ static final List TEST_REF_WITH_DESCRIPTION = List
+ .of(SpecFile.builder().filePath("openapigenerator/testRefWithDescription/api-test.yml")
+ .apiPackage("com.sngular.multifileplugin.refwithdescription")
+ .modelPackage("com.sngular.multifileplugin.refwithdescription.model")
+ .clientPackage("com.sngular.multifileplugin.refwithdescription.client")
+ .modelNameSuffix("DTO").build());
+
static final List TEST_EXTERNAL_PATH_ITEM_REF_GENERATION = List
.of(SpecFile.builder().filePath("openapigenerator/testExternalPathItemRefsGeneration/api-test.yml")
.apiPackage("com.sngular.multifileplugin.externalpathitemref")
@@ -884,6 +891,25 @@ static Function validateOpenApi31Types() {
DEFAULT_MODEL_API, Collections.emptyList(), null);
}
+ static Function validateRefWithDescription() {
+
+ final String DEFAULT_TARGET_API = "generated/com/sngular/multifileplugin/refwithdescription";
+
+ final String DEFAULT_MODEL_API = "generated/com/sngular/multifileplugin/refwithdescription/model";
+
+ final String COMMON_PATH = "openapigenerator/testRefWithDescription/";
+
+ final String ASSETS_PATH = COMMON_PATH + "assets/";
+
+ final List expectedTestApiFile = List.of(ASSETS_PATH + "OrderApi.java");
+
+ final List expectedTestApiModelFiles = List.of(ASSETS_PATH + "AddressDTO.java",
+ ASSETS_PATH + "CustomerDTO.java", ASSETS_PATH + "OrderDTO.java");
+
+ return path -> commonTest(path, expectedTestApiFile, expectedTestApiModelFiles, DEFAULT_TARGET_API,
+ DEFAULT_MODEL_API, Collections.emptyList(), null);
+ }
+
static Function validateExternalPathItemRefGeneration() {
final String DEFAULT_TARGET_API = "generated/com/sngular/multifileplugin/externalpathitemref";
diff --git a/multiapi-engine/src/test/java/com/sngular/api/generator/plugin/openapi/OpenApiGeneratorTest.java b/multiapi-engine/src/test/java/com/sngular/api/generator/plugin/openapi/OpenApiGeneratorTest.java
index 723356a0..90d3b56e 100644
--- a/multiapi-engine/src/test/java/com/sngular/api/generator/plugin/openapi/OpenApiGeneratorTest.java
+++ b/multiapi-engine/src/test/java/com/sngular/api/generator/plugin/openapi/OpenApiGeneratorTest.java
@@ -85,6 +85,8 @@ static Stream fileSpecToProcess() {
OpenApiGeneratorFixtures.validateExternalRefGeneration()),
Arguments.of("testOpenApi31Types", OpenApiGeneratorFixtures.TEST_OPEN_API_31_TYPES,
OpenApiGeneratorFixtures.validateOpenApi31Types()),
+ Arguments.of("testRefWithDescription", OpenApiGeneratorFixtures.TEST_REF_WITH_DESCRIPTION,
+ OpenApiGeneratorFixtures.validateRefWithDescription()),
Arguments.of("testWebhooks", OpenApiGeneratorFixtures.TEST_WEBHOOKS,
OpenApiGeneratorFixtures.validateWebhooks()),
Arguments.of("testOpenApi31Union", OpenApiGeneratorFixtures.TEST_OPEN_API_31_UNION,
diff --git a/multiapi-engine/src/test/resources/openapigenerator/testRefWithDescription/api-test.yml b/multiapi-engine/src/test/resources/openapigenerator/testRefWithDescription/api-test.yml
new file mode 100644
index 00000000..a3af9431
--- /dev/null
+++ b/multiapi-engine/src/test/resources/openapigenerator/testRefWithDescription/api-test.yml
@@ -0,0 +1,55 @@
+---
+# Regression for OpenAPI 3.1 support: a `$ref` may carry sibling keywords such as
+# `description`. The generator must still resolve the reference regardless of the
+# relative ordering of `$ref` and its siblings (see issue: $ref + description).
+openapi: "3.1.0"
+info:
+ version: 1.0.0
+ title: Order API (OpenAPI 3.1)
+ license:
+ name: MIT
+servers:
+ - url: http://localhost:8080/v1
+tags:
+ - name: order
+paths:
+ /order:
+ get:
+ summary: Get an order
+ operationId: getOrder
+ tags:
+ - order
+ responses:
+ '200':
+ description: The requested order
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Order"
+components:
+ schemas:
+ Order:
+ type: object
+ properties:
+ id:
+ type: string
+ customer:
+ description: The customer that placed the order
+ $ref: "#/components/schemas/Customer"
+ shippingAddress:
+ $ref: "#/components/schemas/Address"
+ description: Where the order is shipped
+ Customer:
+ type: object
+ properties:
+ name:
+ type: string
+ email:
+ type: string
+ Address:
+ type: object
+ properties:
+ street:
+ type: string
+ city:
+ type: string
diff --git a/multiapi-engine/src/test/resources/openapigenerator/testRefWithDescription/assets/AddressDTO.java b/multiapi-engine/src/test/resources/openapigenerator/testRefWithDescription/assets/AddressDTO.java
new file mode 100644
index 00000000..3c776daf
--- /dev/null
+++ b/multiapi-engine/src/test/resources/openapigenerator/testRefWithDescription/assets/AddressDTO.java
@@ -0,0 +1,94 @@
+package com.sngular.multifileplugin.refwithdescription.model;
+
+import java.util.Objects;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+
+@JsonDeserialize(builder = AddressDTO.AddressDTOBuilder.class)
+public class AddressDTO {
+
+ @JsonProperty(value ="city")
+ private String city;
+ @JsonProperty(value ="street")
+ private String street;
+
+ private AddressDTO(AddressDTOBuilder builder) {
+ this.city = builder.city;
+ this.street = builder.street;
+
+ }
+
+ public static AddressDTO.AddressDTOBuilder builder() {
+ return new AddressDTO.AddressDTOBuilder();
+ }
+
+ @JsonPOJOBuilder(buildMethodName = "build", withPrefix = "")
+ public static class AddressDTOBuilder {
+
+ private String city;
+ private String street;
+
+ public AddressDTO.AddressDTOBuilder city(String city) {
+ this.city = city;
+ return this;
+ }
+
+ public AddressDTO.AddressDTOBuilder street(String street) {
+ this.street = street;
+ return this;
+ }
+
+ public AddressDTO build() {
+ AddressDTO addressDTO = new AddressDTO(this);
+ return addressDTO;
+ }
+ }
+
+ @Schema(name = "city", required = false)
+ public String getCity() {
+ return city;
+ }
+ public void setCity(String city) {
+ this.city = city;
+ }
+
+ @Schema(name = "street", required = false)
+ public String getStreet() {
+ return street;
+ }
+ public void setStreet(String street) {
+ this.street = street;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ AddressDTO addressDTO = (AddressDTO) o;
+ return Objects.equals(this.city, addressDTO.city) && Objects.equals(this.street, addressDTO.street);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(city, street);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("AddressDTO{");
+ sb.append(" city:").append(city).append(",");
+ sb.append(" street:").append(street);
+ sb.append("}");
+ return sb.toString();
+ }
+
+
+}
diff --git a/multiapi-engine/src/test/resources/openapigenerator/testRefWithDescription/assets/CustomerDTO.java b/multiapi-engine/src/test/resources/openapigenerator/testRefWithDescription/assets/CustomerDTO.java
new file mode 100644
index 00000000..b63a0424
--- /dev/null
+++ b/multiapi-engine/src/test/resources/openapigenerator/testRefWithDescription/assets/CustomerDTO.java
@@ -0,0 +1,94 @@
+package com.sngular.multifileplugin.refwithdescription.model;
+
+import java.util.Objects;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+
+@JsonDeserialize(builder = CustomerDTO.CustomerDTOBuilder.class)
+public class CustomerDTO {
+
+ @JsonProperty(value ="name")
+ private String name;
+ @JsonProperty(value ="email")
+ private String email;
+
+ private CustomerDTO(CustomerDTOBuilder builder) {
+ this.name = builder.name;
+ this.email = builder.email;
+
+ }
+
+ public static CustomerDTO.CustomerDTOBuilder builder() {
+ return new CustomerDTO.CustomerDTOBuilder();
+ }
+
+ @JsonPOJOBuilder(buildMethodName = "build", withPrefix = "")
+ public static class CustomerDTOBuilder {
+
+ private String name;
+ private String email;
+
+ public CustomerDTO.CustomerDTOBuilder name(String name) {
+ this.name = name;
+ return this;
+ }
+
+ public CustomerDTO.CustomerDTOBuilder email(String email) {
+ this.email = email;
+ return this;
+ }
+
+ public CustomerDTO build() {
+ CustomerDTO customerDTO = new CustomerDTO(this);
+ return customerDTO;
+ }
+ }
+
+ @Schema(name = "name", required = false)
+ public String getName() {
+ return name;
+ }
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @Schema(name = "email", required = false)
+ public String getEmail() {
+ return email;
+ }
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ CustomerDTO customerDTO = (CustomerDTO) o;
+ return Objects.equals(this.name, customerDTO.name) && Objects.equals(this.email, customerDTO.email);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, email);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("CustomerDTO{");
+ sb.append(" name:").append(name).append(",");
+ sb.append(" email:").append(email);
+ sb.append("}");
+ return sb.toString();
+ }
+
+
+}
diff --git a/multiapi-engine/src/test/resources/openapigenerator/testRefWithDescription/assets/OrderApi.java b/multiapi-engine/src/test/resources/openapigenerator/testRefWithDescription/assets/OrderApi.java
new file mode 100644
index 00000000..f7d9d872
--- /dev/null
+++ b/multiapi-engine/src/test/resources/openapigenerator/testRefWithDescription/assets/OrderApi.java
@@ -0,0 +1,46 @@
+package com.sngular.multifileplugin.refwithdescription;
+
+import java.util.Optional;
+import java.util.List;
+import java.util.Map;
+import javax.validation.Valid;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import org.springframework.http.MediaType;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.context.request.NativeWebRequest;
+
+import com.sngular.multifileplugin.refwithdescription.model.OrderDTO;
+
+public interface OrderApi {
+
+ /**
+ * GET /order: Get an order
+ * @return The requested order; (status code 200)
+ */
+
+ @Operation(
+ operationId = "getOrder",
+ summary = "Get an order",
+ tags = {"order"},
+ responses = {
+ @ApiResponse(responseCode = "200", description = "The requested order", content = @Content(mediaType = "application/json", schema = @Schema(implementation = OrderDTO.class)))
+ }
+ )
+ @RequestMapping(
+ method = RequestMethod.GET,
+ value = "/order",
+ produces = {"application/json"}
+ )
+
+ default ResponseEntity getOrder() {
+ return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
+ }
+
+}
diff --git a/multiapi-engine/src/test/resources/openapigenerator/testRefWithDescription/assets/OrderDTO.java b/multiapi-engine/src/test/resources/openapigenerator/testRefWithDescription/assets/OrderDTO.java
new file mode 100644
index 00000000..9e932363
--- /dev/null
+++ b/multiapi-engine/src/test/resources/openapigenerator/testRefWithDescription/assets/OrderDTO.java
@@ -0,0 +1,112 @@
+package com.sngular.multifileplugin.refwithdescription.model;
+
+import java.util.Objects;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+
+@JsonDeserialize(builder = OrderDTO.OrderDTOBuilder.class)
+public class OrderDTO {
+
+ @JsonProperty(value ="id")
+ private String id;
+ @JsonProperty(value ="customer")
+ private CustomerDTO customer;
+ @JsonProperty(value ="shippingAddress")
+ private AddressDTO shippingAddress;
+
+ private OrderDTO(OrderDTOBuilder builder) {
+ this.id = builder.id;
+ this.customer = builder.customer;
+ this.shippingAddress = builder.shippingAddress;
+
+ }
+
+ public static OrderDTO.OrderDTOBuilder builder() {
+ return new OrderDTO.OrderDTOBuilder();
+ }
+
+ @JsonPOJOBuilder(buildMethodName = "build", withPrefix = "")
+ public static class OrderDTOBuilder {
+
+ private String id;
+ private CustomerDTO customer;
+ private AddressDTO shippingAddress;
+
+ public OrderDTO.OrderDTOBuilder id(String id) {
+ this.id = id;
+ return this;
+ }
+
+ public OrderDTO.OrderDTOBuilder customer(CustomerDTO customer) {
+ this.customer = customer;
+ return this;
+ }
+
+ public OrderDTO.OrderDTOBuilder shippingAddress(AddressDTO shippingAddress) {
+ this.shippingAddress = shippingAddress;
+ return this;
+ }
+
+ public OrderDTO build() {
+ OrderDTO orderDTO = new OrderDTO(this);
+ return orderDTO;
+ }
+ }
+
+ @Schema(name = "id", required = false)
+ public String getId() {
+ return id;
+ }
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ @Schema(name = "customer", required = false)
+ public CustomerDTO getCustomer() {
+ return customer;
+ }
+ public void setCustomer(CustomerDTO customer) {
+ this.customer = customer;
+ }
+
+ @Schema(name = "shippingAddress", required = false)
+ public AddressDTO getShippingAddress() {
+ return shippingAddress;
+ }
+ public void setShippingAddress(AddressDTO shippingAddress) {
+ this.shippingAddress = shippingAddress;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ OrderDTO orderDTO = (OrderDTO) o;
+ return Objects.equals(this.id, orderDTO.id) && Objects.equals(this.customer, orderDTO.customer) && Objects.equals(this.shippingAddress, orderDTO.shippingAddress);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, customer, shippingAddress);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("OrderDTO{");
+ sb.append(" id:").append(id).append(",");
+ sb.append(" customer:").append(customer).append(",");
+ sb.append(" shippingAddress:").append(shippingAddress);
+ sb.append("}");
+ return sb.toString();
+ }
+
+
+}
diff --git a/scs-multiapi-gradle-plugin/build.gradle b/scs-multiapi-gradle-plugin/build.gradle
index 129e16cd..993a8fe3 100644
--- a/scs-multiapi-gradle-plugin/build.gradle
+++ b/scs-multiapi-gradle-plugin/build.gradle
@@ -21,7 +21,7 @@ repositories {
}
group = 'com.sngular'
-version = '6.4.1'
+version = '6.4.2'
def SCSMultiApiPluginGroupId = group
def SCSMultiApiPluginVersion = version
@@ -31,7 +31,7 @@ dependencies {
shadow localGroovy()
shadow gradleApi()
- implementation 'com.sngular:multiapi-engine:6.4.1'
+ implementation 'com.sngular:multiapi-engine:6.4.2'
testImplementation 'org.assertj:assertj-core:3.24.2'
testImplementation 'com.puppycrawl.tools:checkstyle:10.12.3'
testImplementation 'org.junit.platform:junit-platform-launcher:1.9.2'
@@ -100,7 +100,7 @@ testing {
integrationTest(JvmTestSuite) {
dependencies {
- implementation 'com.sngular:scs-multiapi-gradle-plugin:6.4.1'
+ implementation 'com.sngular:scs-multiapi-gradle-plugin:6.4.2'
implementation 'org.assertj:assertj-core:3.24.2'
}
diff --git a/scs-multiapi-maven-plugin/pom.xml b/scs-multiapi-maven-plugin/pom.xml
index 6f3c5e7f..81ee251a 100644
--- a/scs-multiapi-maven-plugin/pom.xml
+++ b/scs-multiapi-maven-plugin/pom.xml
@@ -4,7 +4,7 @@
com.sngular
scs-multiapi-maven-plugin
- 6.4.1
+ 6.4.2
maven-plugin
AsyncApi - OpenApi Code Generator Maven Plugin
@@ -271,7 +271,7 @@
com.sngular
multiapi-engine
- 6.4.1
+ 6.4.2
org.apache.maven