Openapi fake server¶
See example source
If you generate external API client using OpenAPI declaration, you can also generate a fake server implementation from the same file.
Sample client and server build:
mport org.openapitools.generator.gradle.plugin.tasks.GenerateTask
plugins {
id 'org.openapi.generator'
}
java {
withSourcesJar()
}
dependencies {
implementation 'io.dropwizard:dropwizard-forms'
implementation 'com.github.scribejava:scribejava-core:8.3.3'
// OPENAPI CODEGEN
// additional dependency required for codegen, but conflicts with dropwizard (need to disable feature)
implementation 'org.glassfish.jersey.media:jersey-media-json-jackson'
compileOnly 'io.swagger.core.v3:swagger-annotations:2.2.30'
}
// https://openapi-generator.tech/docs/generators/java
openApiGenerate {
generatorName = "java"
inputSpec = "$projectDir/src/main/openapi/petStore.yaml"
outputDir = "$buildDir/petstore/client"
apiPackage = "com.petstore.api"
invokerPackage = "com.petstore"
modelPackage = "com.petstore.api.model"
configOptions = [
library: "jersey3",
dateLibrary: "java8",
openApiNullable: "false",
hideGenerationTimestamp: "true"
]
}
// https://openapi-generator.tech/docs/generators/jaxrs-jersey
tasks.register('openApiGenerateServer', GenerateTask) {
group = 'openapi tools'
generatorName = "jaxrs-jersey"
inputSpec = "$projectDir/src/main/openapi/petStore.yaml"
outputDir = "$buildDir/petstore/server"
apiPackage = "com.petstore.server.api"
invokerPackage = "com.petstore.server"
modelPackage = "com.petstore.server.api.model"
configOptions = [
library : "jersey3",
dateLibrary: "java8",
openApiNullable: "false",
hideGenerationTimestamp: "true"
]
}
compileJava.dependsOn 'openApiGenerate', 'openApiGenerateServer'
tasks.sourcesJar.dependsOn 'openApiGenerate', 'openApiGenerateServer'
sourceSets.main.java.srcDir "${openApiGenerate.outputDir.get()}/src/main/java"
// note main folder not attached! (sources were copied manually)
sourceSets.main.java.srcDir "${openApiGenerateServer.outputDir.get()}/src/gen/java"
openApiGenerate
creates client in build/petstore/client
openApiGenerateServer
creates server stub in build/petstore/server
Client¶
Actual client interfaces are generated in:
/build/petstore/client/src/main/java/com/petstore/api
These are the main client classes:
PetApi
StoreApi
UserApi
Guice bindings:
public class PetStoreApiModule extends DropwizardAwareModule<ExampleConfig> {
@Override
protected void configure() {
ApiClient apiClient = new ApiClient();
apiClient.setBasePath(configuration().getPetStoreUrl());
// optional
apiClient.setDebugging(true);
bind(ApiClient.class).toInstance(apiClient);
bind(PetApi.class).toInstance(new PetApi(apiClient));
bind(StoreApi.class).toInstance(new StoreApi(apiClient));
bind(UserApi.class).toInstance(new UserApi(apiClient));
}
}
(target url is in configuration)
Server¶
Generated server stub:
/build/petstore/server/src/main/java/com/petstore/server
NOTE: server will also generate its own model, but this part will be attached directly (from gen
folder).
Now copy server files into the main sources (preserving package):
/build/petstore/server/src/main/java/com/petstore/server --> /src/main/java/com/petstore/server
(except Bootstrap
class)
Implement fakes in impl
. For example, to implement getPetById, change PetApiServiceImpl
:
@Override
public Response getPetById(Long petId, SecurityContext securityContext) throws NotFoundException {
final Pet pet = new Pet();
pet.setId(petId);
pet.setName("Jack");
final Tag tag = new Tag();
tag.setName("puppy");
pet.getTags().add(tag);
return Response.ok().entity(pet).build();
}
Now implement root fake resource:
@Path("/fake/petstore/")
public class FakePetStoreServer {
// IMPORTANT: paths in console would contain /pet/pet duplicate, but ACTUAL path matching would IGNORE
// @Path("/pet") declared on ApiApi class, so such declaration is correct for runtime
@Path("/pet")
public Class<PetApi> getPetApi() {
return PetApi.class;
}
@Path("/store")
public Class<StoreApi> getStoreApi() {
return StoreApi.class;
}
@Path("/user")
public Class<UserApi> getUserApi() {
return UserApi.class;
}
}
Bundle¶
Use GuiceyBundle for activation:
public class PetStoreBundle implements GuiceyBundle {
@Override
public void run(GuiceyEnvironment environment) throws Exception {
// because of required conflicting dependency jersey-media-json-jackson
// https://github.com/dropwizard/dropwizard/issues/1341#issuecomment-251503011
environment.environment().jersey()
.property(CommonProperties.FEATURE_AUTO_DISCOVERY_DISABLE, Boolean.TRUE);
// register client api
environment.modules(new PetStoreApiModule());
// optional fake server start
if (environment.<ExampleConfig>configuration().isStartFakeStore()) {
environment.register(FakePetStoreServer.class);
}
}
}
Registration in app:
@Override
public void initialize(Bootstrap<ExampleConfig> bootstrap) {
bootstrap.addBundle(GuiceBundle.builder()
.bundles(new PetStoreBundle())
.build());
}
Usage¶
@TestDropwizardApp(value = ExampleApp.class,
configOverride = {
"petStoreUrl: http://localhost:8080/fake/petstore",
"startFakeStore: true"})
public class FakeServerTest {
@Inject
SampleService sampleService;
@Test
void testServer() {
final Pet pet = sampleService.findPet(1);
Assertions.assertNotNull(pet);
Assertions.assertEquals("Jack", pet.getName());
}
}