Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(okhttp-rest-consumer-from-swagger): Generates Rest consumer driven adapter for imperative projects #411

Merged
merged 2 commits into from
Dec 18, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ public void buildModule(ModuleBuilder builder) throws IOException, CleanExceptio
builder.isKotlin(), "org.springframework.boot:spring-boot-starter-webflux");
builder.appendDependencyToModule(APP_SERVICE, implementation);
builder.appendToProperties("adapter.restconsumer").put("timeout", 5000);
Swagger.fromBuilder(builder, "infrastructure/driven-adapters/rest-consumer", false);
} else {
logger.lifecycle("Generating rest-consumer for imperative project");
builder.setupFromTemplate("driven-adapter/consumer-rest/rest-consumer");
Expand All @@ -43,6 +42,7 @@ public void buildModule(ModuleBuilder builder) throws IOException, CleanExceptio
builder.appendToSettings("rest-consumer", "infrastructure/driven-adapters");
String dependency = buildImplementationFromProject(builder.isKotlin(), ":rest-consumer");
builder.appendDependencyToModule(APP_SERVICE, dependency);
Swagger.fromBuilder(builder, "infrastructure/driven-adapters/rest-consumer", false);
}

private void withCircuitBreaker(ObjectNode instance) {
Expand Down
100 changes: 62 additions & 38 deletions src/main/java/co/com/bancolombia/utils/swagger/Swagger.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,69 +4,93 @@
import io.swagger.codegen.v3.ClientOptInput;
import io.swagger.codegen.v3.DefaultGenerator;
import io.swagger.codegen.v3.config.CodegenConfigurator;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import lombok.Builder;
import lombok.Getter;
import lombok.experimental.UtilityClass;
import org.gradle.api.logging.Logger;

@UtilityClass
public class Swagger {

public static void fromBuilder(ModuleBuilder builder, String outputDir, boolean entryPoint) {
String swagger = builder.getStringParam("swagger-file");
if (swagger != null) {
Logger logger = builder.getProject().getLogger();
String realOutputDir = builder.getProject().getRootDir() + "/" + outputDir;

String packageName = builder.getStringParam("package");
if (!entryPoint) {
packageName = packageName + ".consumer";
}
if (builder.getStringParam("swagger-file") != null) {
generate(
SwaggerOptions.builder()
.reactive(builder.isReactive())
.lombok(builder.isEnableLombok())
.entryPoint(entryPoint)
.router(builder.getBooleanParam("task-param-router"))
.swagger(builder.getStringParam("swagger-file"))
.logger(builder.getProject().getLogger())
.outputDir(builder.getProject().getRootDir() + "/" + outputDir)
.packageName(
entryPoint
? builder.getStringParam("package")
: builder.getStringParam("package") + ".consumer")
.build());
}
}

// Generator setup
CodegenConfigurator configurator = new CodegenConfigurator();
public static void generate(SwaggerOptions options) {
CodegenConfigurator configurator = new CodegenConfigurator();

configurator.addAdditionalProperty("async", builder.isReactive());
configurator.addAdditionalProperty("lombok", builder.isEnableLombok());
configurator.addAdditionalProperty("invokerPackage", packageName + ".api");
configurator.addAdditionalProperty("fullController", true);
configurator.addAdditionalProperty("jakarta", true);
configurator.addAdditionalProperty("useBeanValidation", false);
configurator.addAdditionalProperty("async", options.isReactive());
configurator.addAdditionalProperty("lombok", options.isLombok());
configurator.addAdditionalProperty("invokerPackage", options.getPackageName() + ".api");
configurator.addAdditionalProperty("fullController", true);
configurator.addAdditionalProperty("jakarta", true);
configurator.addAdditionalProperty("useBeanValidation", false);

configurator.setInputSpecURL(swagger);
configurator.setOutputDir(realOutputDir);
configurator.setApiPackage(packageName + ".api");
configurator.setModelPackage(packageName + ".api.model");
configurator.setInputSpecURL(options.getSwagger());
configurator.setOutputDir(options.getOutputDir());
configurator.setApiPackage(options.getPackageName() + ".api");
configurator.setModelPackage(options.getPackageName() + ".api.model");

configurator.setLang(resolveLang(builder, entryPoint));
configurator.setLang(resolveLang(options));

final ClientOptInput clientOptInput = configurator.toClientOptInput();
new DefaultGenerator()
.opts(clientOptInput)
.generate()
.forEach(file -> logger.lifecycle("File generated from swagger: {}", file));
try {
Files.deleteIfExists(Path.of(realOutputDir, ".swagger-codegen-ignore"));
Files.deleteIfExists(Path.of(realOutputDir, ".swagger-codegen", "VERSION"));
Files.deleteIfExists(Path.of(realOutputDir, ".swagger-codegen"));
} catch (IOException e) {
logger.info("file not found", e);
}
final ClientOptInput clientOptInput = configurator.toClientOptInput();
List<File> files = new DefaultGenerator().opts(clientOptInput).generate();
if (options.getLogger() != null) {
files.forEach(file -> options.getLogger().lifecycle("File generated from swagger: {}", file));
}
try {
Files.deleteIfExists(Path.of(options.getOutputDir(), ".swagger-codegen-ignore"));
Files.deleteIfExists(Path.of(options.getOutputDir(), ".swagger-codegen", "VERSION"));
Files.deleteIfExists(Path.of(options.getOutputDir(), ".swagger-codegen"));
} catch (IOException e) {
options.getLogger().info("file not found", e);
}
}

private String resolveLang(ModuleBuilder builder, boolean entryPoint) {
if (entryPoint) {
if (builder.isReactive() && builder.getBooleanParam("task-param-router")) {
private String resolveLang(SwaggerOptions options) {
if (options.isEntryPoint()) {
if (options.isReactive() && options.isRouter()) {
return "io.swagger.codegen.v3.generators.WebFluxRouterCodegen";
} else {
return "io.swagger.codegen.v3.generators.RestControllerCodegen";
}
} else if (builder.isReactive()) {
} else if (options.isReactive()) {
return "io.swagger.codegen.v3.generators.WebClientCodegen";
} else {
return "io.swagger.codegen.v3.generators.RestConsumerCodegen";
}
juancgalvis marked this conversation as resolved.
Show resolved Hide resolved
}

@Builder
@Getter
public static class SwaggerOptions {
private final boolean entryPoint;
private final boolean reactive;
private final boolean lombok;
private final boolean router;
private final String swagger;
private final String packageName;
private final String outputDir;
private final Logger logger;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.swagger.codegen.v3.generators;

public class RestConsumerCodegen extends AbstractScaffoldCodegen {

public RestConsumerCodegen() {
super();
}

@Override
public String getName() {
return "RestConsumer";
}

@Override
public String getHelp() {
return "Generates an entrypoint rest with okhttp.";
}

@Override
public void processOpts() {
super.processOpts();
apiTemplateFiles.put("apiClientImperative.mustache", ".java");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package {{package}};

{{#imports}}import {{import}};
{{/imports}}

import com.fasterxml.jackson.databind.ObjectMapper;
{{#lombok}}
import lombok.extern.log4j.Log4j2;
{{/lombok}}
import okhttp3.FormBody;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.IOException;
{{^fullJavaUtil}}
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
{{/fullJavaUtil}}

{{#operations}}
{{#lombok}}
@Log4j2
{{/lombok}}
@Service
public class {{classname}} {
{{^lombok}}
private static final Logger log = LoggerFactory.getLogger({{classname}}Controller.class);
{{/lombok}}
private final String url;
private final OkHttpClient client;
private final ObjectMapper mapper;

public {{classname}}(@Value("${adapter.restconsumer.url}") String url, OkHttpClient client, ObjectMapper mapper) {
this.url = url;
this.client = client;
this.mapper = mapper;
}

{{#operation}}
{{#contents}}
/**
* Build call for {{operationId}}{{#parameters}}
* @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}{{/parameters}}{{#returnType}}
* @return {{{returnType}}} response{{/returnType}}
{{#externalDocs}}
* {{description}}
* @see <a href="{{url}}">{{summary}} Documentation</a>
{{/externalDocs}}
*/
public {{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}} {{operationId}}Request({{#parameters}}{{{dataType}}} {{paramName}}{{#hasMore}}, {{/hasMore}}{{/parameters}}) throws IOException {
Request okhttpRequest = new Request.Builder()
.url(url + "{{{path}}}"{{#pathParams}}
.replaceAll("\\{" + "{{baseName}}" + "\\}", {{localVariablePrefix}}{{{paramName}}}.toString()){{/pathParams}}){{#headerParams}}
.addHeader("{{baseName}}", {{paramName}}){{/headerParams}}{{#consumes}}{{#@first}}
.addHeader("content-type", "{{{mediaType}}}"){{/@first}}{{/consumes}}{{#produces}}{{#@first}}
.addHeader("accept", "{{{mediaType}}}"){{/@first}}{{/produces}}{{^isForm}}{{#bodyParam}}
.method("{{httpMethod}}", RequestBody.create(mapper.writeValueAsString({{paramName}}), MediaType.parse({{#consumes}}{{#@first}}"{{{mediaType}}}"{{/@first}}{{/consumes}}))){{/bodyParam}}{{^bodyParam}}
.method("{{httpMethod}}", null){{/bodyParam}}{{/isForm}}{{#isForm}}{{#formParams}}
{{#@first}}.method("{{httpMethod}}", new FormBody.Builder().add("{{baseName}}", {{paramName}}){{/@first}}{{^@first}} .add("{{baseName}}", {{paramName}}){{/@first}}{{#@last}}.build()){{/@last}}{{/formParams}}{{/isForm}}
.build();

{{#returnType}}return {{/returnType}}callAndMap(okhttpRequest{{#returnType}}, {{{returnType}}}.class{{/returnType}});
}
{{/contents}}
{{/operation}}

private void callAndMap(Request request) throws IOException {
callAndMap(request, Void.class);
}

private <T> T callAndMap(Request request, Class<T> clazz) throws IOException {
try (Response response = client.newCall(request).execute()) {
if (response.isSuccessful()) {
ResponseBody body = response.body();
if (body != null) {
return mapper.readValue(body.toString(), clazz);
}
return null;
}
throw new IOException(response.toString());
}
}
}
{{/operations}}
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,25 @@ public void generateRestConsumer() throws IOException, CleanException {
.exists());
}

@Test
public void generateRestConsumerFromSwagger() throws IOException, CleanException {
// Arrange
task.setType("RESTCONSUMER");
task.setUrl("http://localhost:8080");
task.setFromSwagger(GenerateEntryPointTaskTest.SWAGGER_FILE);
// Act
task.execute();
// Assert
assertTrue(
new File(
"build/unitTest/infrastructure/driven-adapters/rest-consumer/src/main/java/co/com/bancolombia/consumer/api/PetApi.java")
.exists());
assertTrue(
new File(
"build/unitTest/infrastructure/driven-adapters/rest-consumer/src/main/java/co/com/bancolombia/consumer/api/model/Pet.java")
.exists());
}

@Test
public void generateRsocketRequester() throws IOException, CleanException {
// Arrange
Expand Down
Loading