Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion multiapi-engine/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

<groupId>com.sngular</groupId>
<artifactId>multiapi-engine</artifactId>
<version>6.3.3</version>
<version>6.4.0</version>
<packaging>jar</packaging>

<properties>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ public final class TypeConstants {

public static final String ARRAY = "array";

public static final String NULL = "null";

public static final String MAP = "map";

public static final String BIG_DECIMAL = "bigDecimal";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,23 @@ public static boolean hasType(final JsonNode schema) {
}

public static String getType(final JsonNode schema) {
return hasType(schema) ? StringUtils.defaultIfEmpty(getNodeAsString(schema, "type"), "") : "";
if (!hasType(schema)) {
return "";
}
final JsonNode typeNode = getNode(schema, "type");
if (typeNode.isArray()) {
// OpenAPI 3.1 / JSON Schema 2020-12: "type" may be an array (e.g. ["string", "null"]).
// Resolve to the first non-"null" entry; "null" only marks the type as nullable.
String resolvedType = "";
for (final JsonNode element : typeNode) {
if (!TypeConstants.NULL.equalsIgnoreCase(element.asText())) {
resolvedType = element.asText();
break;
}
}
return resolvedType;
}
return StringUtils.defaultIfEmpty(typeNode.textValue(), "");
}

public static boolean hasItems(final JsonNode schema) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ private static boolean checkIfNumber(final String nodeType) {

private static String processNumber(final JsonNode schema) {

final var nodeType = schema.get("type").asText();
final var nodeType = ApiTool.getType(schema);
final var formatType = schema.has("format") ? schema.get("format").asText() : null;
String type = TypeConstants.INTEGER;
if (TypeConstants.NUMBER.equalsIgnoreCase(nodeType)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,13 @@ public final class OpenApiGeneratorFixtures {
.clientPackage("com.sngular.multifileplugin.externalref.client").modelNamePrefix("Api")
.modelNameSuffix("DTO").build());

static final List<SpecFile> TEST_OPEN_API_31_TYPES = List
.of(SpecFile.builder().filePath("openapigenerator/testOpenApi31Types/api-test.yml")
.apiPackage("com.sngular.multifileplugin.openapi31types")
.modelPackage("com.sngular.multifileplugin.openapi31types.model")
.clientPackage("com.sngular.multifileplugin.openapi31types.client")
.modelNameSuffix("DTO").build());

static final List<SpecFile> TEST_EXTERNAL_PATH_ITEM_REF_GENERATION = List
.of(SpecFile.builder().filePath("openapigenerator/testExternalPathItemRefsGeneration/api-test.yml")
.apiPackage("com.sngular.multifileplugin.externalpathitemref")
Expand Down Expand Up @@ -770,6 +777,24 @@ static Function<Path, Boolean> validateExternalRefGeneration() {
DEFAULT_MODEL_API, expectedExceptionFiles, DEFAULT_EXCEPTION_API);
}

static Function<Path, Boolean> validateOpenApi31Types() {

final String DEFAULT_TARGET_API = "generated/com/sngular/multifileplugin/openapi31types";

final String DEFAULT_MODEL_API = "generated/com/sngular/multifileplugin/openapi31types/model";

final String COMMON_PATH = "openapigenerator/testOpenApi31Types/";

final String ASSETS_PATH = COMMON_PATH + "assets/";

final List<String> expectedTestApiFile = List.of(ASSETS_PATH + "ProfileApi.java");

final List<String> expectedTestApiModelFiles = List.of(ASSETS_PATH + "ProfileDTO.java");

return path -> commonTest(path, expectedTestApiFile, expectedTestApiModelFiles, DEFAULT_TARGET_API,
DEFAULT_MODEL_API, Collections.emptyList(), null);
}

static Function<Path, Boolean> validateExternalPathItemRefGeneration() {

final String DEFAULT_TARGET_API = "generated/com/sngular/multifileplugin/externalpathitemref";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ static Stream<Arguments> fileSpecToProcess() {
OpenApiGeneratorFixtures.validateEnumsLombokGeneration()),
Arguments.of("testExternalRefsGeneration", OpenApiGeneratorFixtures.TEST_EXTERNAL_REF_GENERATION,
OpenApiGeneratorFixtures.validateExternalRefGeneration()),
Arguments.of("testOpenApi31Types", OpenApiGeneratorFixtures.TEST_OPEN_API_31_TYPES,
OpenApiGeneratorFixtures.validateOpenApi31Types()),
Arguments.of("testExternalPathItemRefsGeneration", OpenApiGeneratorFixtures.TEST_EXTERNAL_PATH_ITEM_REF_GENERATION,
OpenApiGeneratorFixtures.validateExternalPathItemRefGeneration()),
Arguments.of("testAnyOfInResponse", OpenApiGeneratorFixtures.TEST_ANY_OF_IN_RESPONSE,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
---
# Reproduction/regression for OpenAPI 3.1.x support (issue #375).
# Exercises JSON Schema 2020-12 array-valued `type`, including the ["<type>", "null"]
# nullable idiom that replaces 3.0's `nullable: true`, plus a int64 and a plain array.
openapi: "3.1.0"
info:
version: 1.0.0
title: Profile API (OpenAPI 3.1)
license:
name: MIT
servers:
- url: http://localhost:8080/v1
tags:
- name: profile
paths:
/profile:
get:
summary: Get the profile
operationId: getProfile
tags:
- profile
responses:
'200':
description: The requested profile
content:
application/json:
schema:
$ref: "#/components/schemas/Profile"
components:
schemas:
Profile:
type: object
properties:
id:
type: string
nickname:
type: ["string", "null"]
age:
type: ["integer", "null"]
score:
type: ["number", "null"]
format: double
loginCount:
type: integer
format: int64
tags:
type: array
items:
type: string
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.sngular.multifileplugin.openapi31types;

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.openapi31types.model.ProfileDTO;

public interface ProfileApi {

/**
* GET /profile: Get the profile
* @return The requested profile; (status code 200)
*/

@Operation(
operationId = "getProfile",
summary = "Get the profile",
tags = {"profile"},
responses = {
@ApiResponse(responseCode = "200", description = "The requested profile", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ProfileDTO.class)))
}
)
@RequestMapping(
method = RequestMethod.GET,
value = "/profile",
produces = {"application/json"}
)

default ResponseEntity<ProfileDTO> getProfile() {
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
package com.sngular.multifileplugin.openapi31types.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;
import java.util.List;
import java.util.ArrayList;

@JsonDeserialize(builder = ProfileDTO.ProfileDTOBuilder.class)
public class ProfileDTO {

@JsonProperty(value ="tags")
private List<String> tags;
@JsonProperty(value ="id")
private String id;
@JsonProperty(value ="loginCount")
private Long loginCount;
@JsonProperty(value ="age")
private Integer age;
@JsonProperty(value ="score")
private Double score;
@JsonProperty(value ="nickname")
private String nickname;

private ProfileDTO(ProfileDTOBuilder builder) {
this.tags = builder.tags;
this.id = builder.id;
this.loginCount = builder.loginCount;
this.age = builder.age;
this.score = builder.score;
this.nickname = builder.nickname;

}

public static ProfileDTO.ProfileDTOBuilder builder() {
return new ProfileDTO.ProfileDTOBuilder();
}

@JsonPOJOBuilder(buildMethodName = "build", withPrefix = "")
public static class ProfileDTOBuilder {

private List<String> tags = new ArrayList<String>();
private String id;
private Long loginCount;
private Integer age;
private Double score;
private String nickname;

public ProfileDTO.ProfileDTOBuilder tags(List<String> tags) {
if (!tags.isEmpty()) {
this.tags.addAll(tags);
}
return this;
}

public ProfileDTO.ProfileDTOBuilder tag(String tag) {
if (Objects.nonNull(tag)) {
this.tags.add(tag);
}
return this;
}

public ProfileDTO.ProfileDTOBuilder id(String id) {
this.id = id;
return this;
}

public ProfileDTO.ProfileDTOBuilder loginCount(Long loginCount) {
this.loginCount = loginCount;
return this;
}

public ProfileDTO.ProfileDTOBuilder age(Integer age) {
this.age = age;
return this;
}

public ProfileDTO.ProfileDTOBuilder score(Double score) {
this.score = score;
return this;
}

public ProfileDTO.ProfileDTOBuilder nickname(String nickname) {
this.nickname = nickname;
return this;
}

public ProfileDTO build() {
ProfileDTO profileDTO = new ProfileDTO(this);
return profileDTO;
}
}

@Schema(name = "tags", required = false)
public List<String> getTags() {
return tags;
}
public void setTags(List<String> tags) {
this.tags = tags;
}

@Schema(name = "id", required = false)
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}

@Schema(name = "loginCount", required = false)
public Long getLoginCount() {
return loginCount;
}
public void setLoginCount(Long loginCount) {
this.loginCount = loginCount;
}

@Schema(name = "age", required = false)
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}

@Schema(name = "score", required = false)
public Double getScore() {
return score;
}
public void setScore(Double score) {
this.score = score;
}

@Schema(name = "nickname", required = false)
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ProfileDTO profileDTO = (ProfileDTO) o;
return Objects.equals(this.tags, profileDTO.tags) && Objects.equals(this.id, profileDTO.id) && Objects.equals(this.loginCount, profileDTO.loginCount) && Objects.equals(this.age, profileDTO.age) && Objects.equals(this.score, profileDTO.score) && Objects.equals(this.nickname, profileDTO.nickname);
}

@Override
public int hashCode() {
return Objects.hash(tags, id, loginCount, age, score, nickname);
}

@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("ProfileDTO{");
sb.append(" tags:").append(tags).append(",");
sb.append(" id:").append(id).append(",");
sb.append(" loginCount:").append(loginCount).append(",");
sb.append(" age:").append(age).append(",");
sb.append(" score:").append(score).append(",");
sb.append(" nickname:").append(nickname);
sb.append("}");
return sb.toString();
}


}
Loading
Loading