Skip to content

Commit

Permalink
Support and test actuator specs
Browse files Browse the repository at this point in the history
  • Loading branch information
dsyer committed Oct 10, 2023
1 parent 6187a9a commit 7120364
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 2 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ Features:

* Spec conversion. If any of the specs is Swagger (OpenAPI 2.0) instead of OpenAPI 3.0 the aggregator will automatically convert it to OpenAPI 3.0. OpenAPI 3.1 is not supported yet, but it probably wouldn't be hard.
* Spec filtering. You can apply arbitrary filters to component specs, and to the aggregated result. For instance, you can remove the `info` section from the aggregated spec, or remove the `paths` section from the Wizards API spec.
* [SpringDoc](https://github.com/springdoc/springdoc-openapi) integration. The aggregator uses the SpringDoc-generated spec as a base if it exists, so the application can provide its own endpoints and have them automatically added. Once SpringDoc is on the classpath the spec endpoint at `/v3/api-docs` is configured through the normal SpringDoc options (the endpoint created by this library backs off).
* Convenience methods for common filters. For instance, you can add path prefixes (as in the example above), rename operations, or rename schema objects. Cross references are updated automatically.
* External configuration. Set `spring.openapi.base.*` to be an `OpenAPI` spec that will be merged with the aggregated spec. This is useful for adding info, or global security definitions, for instance. And set `spring.openapi.aggregator.path` to configure the HTTP endpoint path (default `/v3/api-docs`). See `application.yml` in the tests for an example.
24 changes: 23 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,25 +29,47 @@
</dependency>
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-core</artifactId>
<artifactId>swagger-core-jakarta</artifactId>
<version>2.2.9</version>
</dependency>
<dependency>
<groupId>io.swagger.parser.v3</groupId>
<artifactId>swagger-parser-v2-converter</artifactId>
<version>2.1.16</version>
<exclusions>
<exclusion>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-core</artifactId>
</exclusion>
<exclusion>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-models</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webflux-api</artifactId>
<version>2.0.2</version>
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,28 @@
*/
package org.springframework.openapi.aggregator;

import java.util.List;
import java.util.Optional;

import org.springdoc.core.configuration.SpringDocConfiguration;
import org.springdoc.core.customizers.OpenApiBuilderCustomizer;
import org.springdoc.core.customizers.ServerBaseUrlCustomizer;
import org.springdoc.core.properties.SpringDocConfigProperties;
import org.springdoc.core.providers.JavadocProvider;
import org.springdoc.core.service.OpenAPIService;
import org.springdoc.core.service.SecurityService;
import org.springdoc.core.utils.PropertyResolverUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

Expand All @@ -40,6 +55,8 @@
*/
@Configuration
@ConditionalOnBean(OpenApiAggregatorSpecs.class)
@AutoConfigureBefore(SpringDocConfiguration.class)
@Import(SpringdocConfiguration.class)
public class OpenApiAggregatorConfiguration {

/**
Expand Down Expand Up @@ -76,12 +93,29 @@ public OpenAPI base() {
*/
@Bean
@ConditionalOnWebApplication
@ConditionalOnMissingBean(type = "org.springdoc.core.service.OpenAPIService")
public AggregatorEndpoint aggregatorEndpoint(OpenApiAggregator aggregator) {
return new AggregatorEndpoint(aggregator);
}

}

@Configuration
@ConditionalOnClass(OpenAPIService.class)
class SpringdocConfiguration {

@Bean
OpenAPIService openAPIBuilder(OpenApiAggregator aggregator, SecurityService securityParser,
SpringDocConfigProperties springDocConfigProperties, PropertyResolverUtils propertyResolverUtils,
Optional<List<OpenApiBuilderCustomizer>> openApiBuilderCustomisers,
Optional<List<ServerBaseUrlCustomizer>> serverBaseUrlCustomisers,
Optional<JavadocProvider> javadocProvider) {
return new OpenAPIService(Optional.of(aggregator.aggregate()), securityParser, springDocConfigProperties,
propertyResolverUtils, openApiBuilderCustomisers, serverBaseUrlCustomisers, javadocProvider);
}

}

@RestController
class AggregatorEndpoint implements InitializingBean {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package org.springframework.openapi.aggregator;

import static org.assertj.core.api.Assertions.assertThat;

import java.util.Locale;

import org.junit.jupiter.api.Test;
import org.springdoc.core.configuration.SpringDocConfiguration;
import org.springdoc.core.properties.SpringDocConfigProperties;
import org.springdoc.core.service.OpenAPIService;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;

public class AutoconfigurationTests {

@Test
public void withoutSpringdoc() {
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(OpenApiAggregatorConfiguration.class))
.withBean(OpenApiAggregatorSpecs.class, () -> new OpenApiAggregatorSpecs())
.withClassLoader(new FilteredClassLoader(OpenAPIService.class));
contextRunner.run(context -> {
assertThat(context.getBeanNamesForType(OpenApiAggregator.class)).isNotEmpty();
assertThat(context.getBeanNamesForType(AggregatorEndpoint.class)).isEmpty();
});
}

@Test
public void withSpringdoc() {
ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(OpenApiAggregatorConfiguration.class, SpringDocConfiguration.class,
SpringDocConfigProperties.class, WebFluxAutoConfiguration.class))
.withBean(OpenApiAggregatorSpecs.class, () -> new OpenApiAggregatorSpecs());
contextRunner.run(context -> {
assertThat(context.getBeanNamesForType(OpenApiAggregator.class)).isNotEmpty();
assertThat(context.getBeanNamesForType(OpenAPIService.class)).isNotEmpty();
assertThat(context.getBean(OpenAPIService.class).build(Locale.US)).isNotNull();
});
}

@Test
public void plainWebApp() {
ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner()
.withConfiguration(
AutoConfigurations.of(OpenApiAggregatorConfiguration.class, WebFluxAutoConfiguration.class))
.withBean(OpenApiAggregatorSpecs.class, () -> new OpenApiAggregatorSpecs())
.withClassLoader(new FilteredClassLoader(OpenAPIService.class));
contextRunner.run(context -> {
assertThat(context.getBeanNamesForType(OpenApiAggregator.class)).isNotEmpty();
assertThat(context.getBeanNamesForType(AggregatorEndpoint.class)).isNotEmpty();
assertThat(context.getBeanNamesForType(OpenAPIService.class)).isEmpty();
});

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public class GatewayApplication {
private String wizards = "https://wizard-world-api.herokuapp.com";

public static void main(String[] args) {
System.setProperty("management.endpoints.web.exposure.include", "*");
SpringApplication.run(GatewayApplication.class, args);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ void contextLoads() throws Exception {
assertThat(response.getBody()).doesNotContain("\"style\": \"form\"");
OpenAPI api = mapper.readValue(response.getBody(), OpenAPI.class);
assertThat(api.getPaths()).containsKeys("/dates/AvailableCountries", "/wizards/Elixirs");
assertThat(api.getPaths()).containsKeys("/actuator/health");
assertThat(api.getInfo().getDescription()).isEqualTo("Gateway API In Test");
}

Expand Down
6 changes: 5 additions & 1 deletion src/test/resources/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,8 @@ spring:
info:
title: My API
description: My API description
version: 1.0.0
version: 1.0.0
aggregator:
path: /api-docs
springdoc:
show-actuator: true

0 comments on commit 7120364

Please sign in to comment.