Skip to content

Commit

Permalink
Fix openapi 3.1 document generation
Browse files Browse the repository at this point in the history
  • Loading branch information
justin-tay committed Jul 8, 2024
1 parent eae4d98 commit 191761c
Show file tree
Hide file tree
Showing 12 changed files with 122 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import com.yahoo.elide.graphql.GraphQLSettings;
import com.yahoo.elide.jsonapi.JsonApiSettings;
import com.yahoo.elide.swagger.OpenApiBuilder;
import com.yahoo.elide.swagger.OpenApiDocument;
import com.yahoo.elide.swagger.resources.ApiDocsEndpoint;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.hibernate.Session;
Expand Down Expand Up @@ -131,8 +130,7 @@ public List<ApiDocsEndpoint.ApiDocsRegistration> buildSwagger(Elide elide) {
OpenApiBuilder builder = new OpenApiBuilder(dictionary).apiVersion(apiVersion);
String moduleBasePath = "/apiDocs/";
OpenAPI openApi = builder.build().info(info).addServersItem(new Server().url(moduleBasePath));
docs.add(new ApiDocsEndpoint.ApiDocsRegistration("api", () -> openApi,
OpenApiDocument.Version.OPENAPI_3_0.getValue(), apiVersion));
docs.add(new ApiDocsEndpoint.ApiDocsRegistration("api", () -> openApi, apiVersion));
});

return docs;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;

/**
* Basic customization of the OpenAPI document.
Expand All @@ -48,9 +49,15 @@ public void customize(OpenAPI openApi) {
}

if (openApi.getInfo() == null) {
openApi.info(new Info().title(OpenApiDocument.DEFAULT_TITLE));
} else if (openApi.getInfo().getTitle() == null || openApi.getInfo().getTitle().isBlank()) {
openApi.getInfo().setTitle(OpenApiDocument.DEFAULT_TITLE);
// Version is a required field
openApi.info(new Info().title(OpenApiDocument.DEFAULT_TITLE).version(""));
} else {
if (openApi.getInfo().getTitle() == null || openApi.getInfo().getTitle().isBlank()) {
openApi.getInfo().setTitle(OpenApiDocument.DEFAULT_TITLE);
}
if (openApi.getInfo().getVersion() == null) {
openApi.getInfo().setVersion("");
}
}

sort(openApi);
Expand Down Expand Up @@ -79,12 +86,10 @@ protected Map<String, io.swagger.v3.oas.models.security.SecurityScheme> getSecur
new io.swagger.v3.oas.models.security.SecurityScheme();
model.setIn(getIn(annotation.in()));
model.setType(getType(annotation.type()));
model.setBearerFormat(annotation.bearerFormat());
model.setScheme(annotation.scheme());
model.setOpenIdConnectUrl(annotation.openIdConnectUrl());
if (annotation.ref() != null && !annotation.ref().isBlank()) {
model.set$ref(annotation.ref());
}
copyNonBlank(annotation.bearerFormat(), model::setBearerFormat);
copyNonBlank(annotation.scheme(), model::setScheme);
copyNonBlank(annotation.openIdConnectUrl(), model::setOpenIdConnectUrl);
copyNonBlank(annotation.ref(), model::set$ref);
model.setName(annotation.name());
securitySchemes.put(annotation.name(), model);
}
Expand All @@ -103,7 +108,20 @@ protected OpenAPIDefinition getOpenApiDefinition() {
}

public static void applyDefinition(OpenAPI openApi, OpenAPIDefinition openApiDefinition) {
AnnotationsUtils.getInfo(openApiDefinition.info()).ifPresent(openApi::setInfo);
AnnotationsUtils.getInfo(openApiDefinition.info()).ifPresent(info -> {
if (openApi.getInfo() == null) {
openApi.setInfo(info);
} else {
// Copy non null
copyNonNull(info.getTitle(), openApi.getInfo()::setTitle);
copyNonNull(info.getDescription(), openApi.getInfo()::setDescription);
copyNonNull(info.getTermsOfService(), openApi.getInfo()::setTermsOfService);
copyNonNull(info.getContact(), openApi.getInfo()::setContact);
copyNonNull(info.getLicense(), openApi.getInfo()::setLicense);
copyNonNull(info.getVersion(), openApi.getInfo()::setVersion);
copyNonNull(info.getExtensions(), openApi.getInfo()::setExtensions);
}
});
AnnotationsUtils.getExternalDocumentation(openApiDefinition.externalDocs()).ifPresent(openApi::setExternalDocs);
AnnotationsUtils.getTags(openApiDefinition.tags(), false).ifPresent(tags -> tags.forEach(openApi::addTagsItem));

Expand All @@ -117,6 +135,18 @@ public static void applyDefinition(OpenAPI openApi, OpenAPIDefinition openApiDef
openApi.addSecurityItem(model);
}

protected static <T> void copyNonNull(T value, Consumer<T> target) {
if (value != null) {
target.accept(value);
}
}

protected static void copyNonBlank(String value, Consumer<String> target) {
if (value != null && !value.isBlank()) {
target.accept(value);
}
}

protected io.swagger.v3.oas.models.security.SecurityScheme.In getIn(SecuritySchemeIn value) {
if (value == null) {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.binder.cache.CaffeineCacheMetrics;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.SpecVersion;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.media.StringSchema;
import io.swagger.v3.oas.models.parameters.Parameter;
Expand Down Expand Up @@ -1115,8 +1116,11 @@ public static ApiDocsController.ApiDocsRegistrations buildApiDocsRegistrations(R
List<ApiDocsRegistration> registrations = new ArrayList<>();
dictionary.getApiVersions().stream().forEach(apiVersion -> {
Supplier<OpenAPI> document = () -> {
OpenApiBuilder builder = new OpenApiBuilder(dictionary).apiVersion(apiVersion)
.supportLegacyFilterDialect(false);
OpenApiBuilder builder = new OpenApiBuilder(dictionary, openApi -> {
if (ApiDocsControllerProperties.Version.OPENAPI_3_1.equals(settings.getApiDocs().getVersion())) {
openApi.specVersion(SpecVersion.V31).openapi("3.1.0");
}
}).apiVersion(apiVersion).supportLegacyFilterDialect(false);
if (!EntityDictionary.NO_VERSION.equals(apiVersion)) {
if (settings.getApiVersioningStrategy().getPath().isEnabled()) {
// Path needs to be set
Expand Down Expand Up @@ -1152,8 +1156,7 @@ public static ApiDocsController.ApiDocsRegistrations buildApiDocsRegistrations(R
customizer.customize(openApi);
return openApi;
};
registrations.add(new ApiDocsRegistration("", SingletonSupplier.of(document),
settings.getApiDocs().getVersion().getValue(), apiVersion));
registrations.add(new ApiDocsRegistration("", SingletonSupplier.of(document), apiVersion));
});
return new ApiDocsController.ApiDocsRegistrations(registrations);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,8 @@ public class ApiDocsController {
@Data
@AllArgsConstructor
public static class ApiDocsRegistrations {

public ApiDocsRegistrations(Supplier<OpenAPI> doc, String version, String apiVersion) {
registrations = List.of(new ApiDocsRegistration("", doc, version, apiVersion));
public ApiDocsRegistrations(Supplier<OpenAPI> doc, String apiVersion) {
registrations = List.of(new ApiDocsRegistration("", doc, apiVersion));
}

List<ApiDocsRegistration> registrations;
Expand All @@ -81,11 +80,6 @@ public static class ApiDocsRegistration {
private String path;
private Supplier<OpenAPI> document;

/**
* The OpenAPI Specification Version.
*/
private String version;

/**
* The API version.
*/
Expand All @@ -109,8 +103,7 @@ public ApiDocsController(ApiDocsRegistrations docs, RouteResolver routeResolver,
apiVersion = apiVersion == null ? NO_VERSION : apiVersion;
String apiPath = doc.path;

this.documents.put(Pair.of(apiVersion, apiPath),
new OpenApiDocument(doc.document, OpenApiDocument.Version.from(doc.version)));
this.documents.put(Pair.of(apiVersion, apiPath), new OpenApiDocument(doc.document));
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class BasicOpenApiDocumentCustomizerTest {
@OpenAPIDefinition(info = @Info(title = "My Title"))
public static class UserDefinitionOpenApiConfiguration {
@Bean
public OpenApiDocumentCustomizer openApiDocumentCustomizer() {
OpenApiDocumentCustomizer openApiDocumentCustomizer() {
return new BasicOpenApiDocumentCustomizer();
}
}
Expand All @@ -43,15 +43,15 @@ public OpenApiDocumentCustomizer openApiDocumentCustomizer() {
@OpenAPIDefinition(info = @Info(description = "My Description"))
public static class UserDefinitionNoTitleOpenApiConfiguration {
@Bean
public OpenApiDocumentCustomizer openApiDocumentCustomizer() {
OpenApiDocumentCustomizer openApiDocumentCustomizer() {
return new BasicOpenApiDocumentCustomizer();
}
}

@Configuration
public static class UserNoDefinitionOpenApiConfiguration {
@Bean
public OpenApiDocumentCustomizer openApiDocumentCustomizer() {
OpenApiDocumentCustomizer openApiDocumentCustomizer() {
return new BasicOpenApiDocumentCustomizer();
}
}
Expand All @@ -66,7 +66,7 @@ public OpenApiDocumentCustomizer openApiDocumentCustomizer() {
)
public static class UserSecurityOpenApiConfiguration {
@Bean
public OpenApiDocumentCustomizer openApiDocumentCustomizer() {
OpenApiDocumentCustomizer openApiDocumentCustomizer() {
return new BasicOpenApiDocumentCustomizer();
}
}
Expand Down Expand Up @@ -123,4 +123,28 @@ void shouldSortTags() {
assertThat(openApi.getTags()).extracting("name").containsExactly("1-test", "a-test", "b-test", "z-test");
});
}

@Test
void shouldUseOpenApiDefinitionExceptNonNullExisting() {
contextRunner.withUserConfiguration(UserDefinitionOpenApiConfiguration.class).run(context -> {
OpenAPI openApi = new OpenAPI();
openApi.setInfo(new io.swagger.v3.oas.models.info.Info().title("Should Be Overridden").version("v2"));
context.getBean(OpenApiDocumentCustomizer.class).customize(openApi);
assertThat(openApi.getInfo().getTitle()).isEqualTo("My Title");
assertThat(openApi.getInfo().getVersion()).isEqualTo("v2");
});
}

/**
* The OpenAPI document /info/version is a required property.
*/
@Test
void shouldNotHaveNullVersion() {
contextRunner.withUserConfiguration(UserDefinitionOpenApiConfiguration.class).run(context -> {
OpenAPI openApi = new OpenAPI();
context.getBean(OpenApiDocumentCustomizer.class).customize(openApi);
assertThat(openApi.getInfo().getVersion()).isNotNull();
assertThat(openApi.getInfo().getVersion()).isEqualTo("");
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
import static org.hamcrest.Matchers.equalTo;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import com.yahoo.elide.core.exceptions.HttpStatus;
Expand Down Expand Up @@ -1129,7 +1128,6 @@ public void apiDocsDocumentTestYaml() throws JsonMappingException, JsonProcessin
assertTrue(tags.isArray());
}


@Test
public void versionedApiDocsDocumentTest() {
ExtractableResponse<Response> v0 = given()
Expand All @@ -1138,6 +1136,7 @@ public void versionedApiDocsDocumentTest() {
.then()
.statusCode(HttpStatus.SC_OK)
.extract();
assertEquals("", v0.path("info.version"));

ExtractableResponse<Response> v1 = given()
.header("ApiVersion", "1.0")
Expand All @@ -1147,7 +1146,6 @@ public void versionedApiDocsDocumentTest() {
.statusCode(HttpStatus.SC_OK)
.extract();
assertNotEquals(v0.asString(), v1.asString());
assertNull(v0.path("info.version"));
assertEquals("1.0", v1.path("info.version"));

given()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
import graphql.execution.SimpleDataFetcherExceptionHandler;

import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.SpecVersion;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.servers.Server;
import jakarta.persistence.EntityManager;
Expand Down Expand Up @@ -471,15 +472,19 @@ default List<ApiDocsEndpoint.ApiDocsRegistration> buildApiDocs(EntityDictionary
Info info = new Info()
.title(getApiTitle())
.version(apiVersion);
OpenApiBuilder builder = new OpenApiBuilder(dictionary).apiVersion(apiVersion);
OpenApiBuilder builder = new OpenApiBuilder(dictionary, openApi -> {
OpenApiVersion openApiVersion = getOpenApiVersion();
if (OpenApiVersion.OPENAPI_3_1.equals(openApiVersion)) {
openApi.specVersion(SpecVersion.V31).openapi("3.1.0");
}
}).apiVersion(apiVersion);
if (!EntityDictionary.NO_VERSION.equals(apiVersion)) {
// Path needs to be set
builder.basePath("/" + "v" + apiVersion);
}
String moduleBasePath = getJsonApiPathSpec().replace("/*", "");
OpenAPI openApi = builder.build().info(info).addServersItem(new Server().url(moduleBasePath));
docs.add(new ApiDocsEndpoint.ApiDocsRegistration("", () -> openApi, getOpenApiVersion().getValue(),
apiVersion));
docs.add(new ApiDocsEndpoint.ApiDocsRegistration("", () -> openApi, apiVersion));
});

return docs;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -684,10 +684,13 @@ private boolean lineageContainsType(PathMetaData other) {

/**
* Constructor.
* <p>
* The customizer can be used to set the OpenAPI SpecVersion.
*
* @param dictionary The entity dictionary.
* @param dictionary The entity dictionary.
* @param openApiCustomizer The OpenAPI customizer.
*/
public OpenApiBuilder(EntityDictionary dictionary) {
public OpenApiBuilder(EntityDictionary dictionary, Consumer<OpenAPI> openApiCustomizer) {
this.dictionary = dictionary;
this.supportLegacyFilterDialect = true;
this.supportRSQLFilterDialect = true;
Expand All @@ -698,6 +701,18 @@ public OpenApiBuilder(EntityDictionary dictionary) {
Operator.POSTFIX, Operator.GE, Operator.GT, Operator.LE, Operator.LT, Operator.ISNULL,
Operator.NOTNULL);
this.openApi = new OpenAPI();
if (openApiCustomizer != null) {
openApiCustomizer.accept(this.openApi);
}
}

/**
* Constructor.
*
* @param dictionary The entity dictionary.
*/
public OpenApiBuilder(EntityDictionary dictionary) {
this(dictionary, null);
}

/**
Expand Down Expand Up @@ -1095,8 +1110,10 @@ protected Set<PathMetaData> find(Type<?> rootClass) {
}

protected String getSchemaName(Type<?> type) {
// Should be the same as JsonApiModelResolver#getSchemaName
String schemaName = dictionary.getJsonAliasFor(type);
if (!EntityDictionary.NO_VERSION.equals(this.apiVersion)) {
String apiVersion = EntityDictionary.getModelVersion(type);
if (!EntityDictionary.NO_VERSION.equals(apiVersion)) {
schemaName = "v" + this.apiVersion + "_" + schemaName;
}
return schemaName;
Expand Down
Loading

0 comments on commit 191761c

Please sign in to comment.