Skip to content

Commit

Permalink
new api generation
Browse files Browse the repository at this point in the history
  • Loading branch information
Thorsten Schlathoelter authored and bbortt committed Dec 9, 2024
1 parent 679fc68 commit 9c38674
Show file tree
Hide file tree
Showing 13 changed files with 2,166 additions and 227 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ public String getEndpointUri() {
/**
* Action builder.
*/
public static final class Builder extends SendMessageActionBuilder<SendMessageAction, SendMessageActionBuilderSupport, Builder> {
public static class Builder extends SendMessageActionBuilder<SendMessageAction, SendMessageActionBuilderSupport, Builder> {

/**
* Fluent API action building entry method used in Java DSL.
Expand Down
5 changes: 5 additions & 0 deletions test-api-generator/citrus-test-api-generator-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@
<artifactId>citrus-http</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.citrusframework</groupId>
<artifactId>citrus-openapi</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.citrusframework</groupId>
<artifactId>citrus-spring</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package org.citrusframework.openapi.generator;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.citrusframework.actions.SendMessageAction;
import org.citrusframework.openapi.OpenApiSpecification;
import org.citrusframework.openapi.actions.OpenApiClientRequestActionBuilder;
import org.springframework.http.MediaType;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

public class TestApiClientRequestActionBuilder extends OpenApiClientRequestActionBuilder {

// TODO: do we really need this?
protected OpenApiSpecification openApiSpec;

private final String path;

private final Map<String, String> pathParameters = new HashMap<>();

private final MultiValueMap<String, Object> formData = new LinkedMultiValueMap<>();

// TODO: can we just pass in the operation?
public TestApiClientRequestActionBuilder(OpenApiSpecification openApiSpec, String method, String path, String operationName) {
super(openApiSpec, "%s_%s".formatted(method, path));
name(String.format("%s:%s", "PetStore".toLowerCase(), operationName));
getMessageBuilderSupport().header("citrus_open_api_operation_name", operationName);
getMessageBuilderSupport().header("citrus_open_api_method", method);
getMessageBuilderSupport().header("citrus_open_api_path", path);

this.openApiSpec = openApiSpec;
this.path = path;
}

protected void pathParameter(String name, String value) {
pathParameters.put(name, value);
}

protected void formData(String name, String value) {
formData.add(name, value);
}

protected String qualifiedPath(String path) {

String qualifiedPath = path;
for (Entry<String, String> entry : pathParameters.entrySet()) {
qualifiedPath = qualifiedPath.replace("{%s}".formatted(entry.getKey()), entry.getValue());
}
return qualifiedPath;
}

protected String toQueryParam(String...arrayElements) {
return String.join(",", arrayElements);
}

@Override
public SendMessageAction doBuild() {
// TODO: register callback to modify builder
path(qualifiedPath(path));
if (!formData.isEmpty()) {
// TODO: do we have to explicitly set the content type or is this done by citrus
messageBuilderSupport.contentType(MediaType.MULTIPART_FORM_DATA_VALUE);
getMessageBuilderSupport().body(formData);
}
return super.doBuild();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,258 +2,83 @@

package {{package}};

import org.citrusframework.testapi.GeneratedApi;
import org.citrusframework.testapi.GeneratedApiRequest;
import jakarta.servlet.http.Cookie;
import org.apache.commons.lang3.StringUtils;
import org.citrusframework.context.TestContext;
import org.citrusframework.exceptions.CitrusRuntimeException;
import org.citrusframework.spi.Resources;
import org.citrusframework.http.actions.HttpActionBuilder;
import org.citrusframework.http.actions.HttpClientRequestActionBuilder;
import org.citrusframework.http.actions.HttpClientRequestActionBuilder.HttpMessageBuilderSupport;
import org.citrusframework.util.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

import {{invokerPackage}}.citrus.{{prefix}}AbstractTestRequest;

import java.io.IOException;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.citrusframework.http.client.HttpClient;
import org.citrusframework.openapi.OpenApiSpecification;
import org.citrusframework.openapi.generator.TestApiClientRequestActionBuilder;
import org.citrusframework.openapi.generator.rest.petstore.model.Pet;
import org.citrusframework.testapi.GeneratedApi;

import {{invokerPackage}}.citrus.{{prefix}}AbstractSendAction;

{{>additionalModelTypeAnnotations}}{{>generatedAnnotation}}{{#discriminator}}{{>typeInfoAnnotation}}{{/discriminator}}{{>xmlAnnotation}}
public class {{classname}} implements GeneratedApi
{
public static final {{classname}} INSTANCE = new {{classname}}();

private OpenApiSpecification openApiSpecification = null;

public String getApiTitle() {
return "{{appName}}";
return "{{appName}}";
}

public String getApiVersion() {
return "{{appVersion}}";
return "{{appVersion}}";
}

public String getApiPrefix() {
return "{{prefix}}";
return "{{prefix}}";
}

public Map<String,String> getApiInfoExtensions() {
Map<String, String> infoExtensionMap = new HashMap<>();
{{#infoExtensions}}
{{#entrySet}}
infoExtensionMap.put("{{key}}", "{{value}}");
infoExtensionMap.put("{{key}}", "{{value}}");
{{/entrySet}}
{{/infoExtensions}}
return infoExtensionMap;
}

{{#operations}}
{{#operations}}
{{#operation}}
/** {{operationId}} ({{httpMethod}} {{httpPathPrefix}}{{{path}}})
{{summary}}
{{description}}
**/
public static class {{operationIdCamelCase}}Request extends {{prefix}}AbstractTestRequest implements GeneratedApiRequest {
private static final String ENDPOINT = "{{httpPathPrefix}}{{{path}}}";
private final Logger coverageLogger = LoggerFactory.getLogger({{operationIdCamelCase}}Request.class);

{{#queryParams}}
private String {{paramName}};

{{/queryParams}}
{{#pathParams}}
private String {{paramName}};

{{/pathParams}}
{{#isMultipart}}
{{#formParams}}
private String {{paramName}};

{{/formParams}}
{{/isMultipart}}
{{#authMethods}}{{#isBasic}}
@Value("${" + "{{apiEndpoint}}.basic.username:#{null}}")
private String basicUsername;
@Value("${" + "{{apiEndpoint}}.basic.password:#{null}}")
private String basicPassword;

{{/isBasic}}
{{/authMethods}}

public {{operationIdCamelCase}}Request() {
// The name will be overwritten with the tag name using the actual namespace as prefix, when the class is loaded from xml
setName("{{prefix}}".toLowerCase() + ":{{operationId}}RequestType");
}

public String getOperationName() {
return "{{operationId}}";
public {{operationIdCamelCase}}ActionBuilder {{operationId}}() {
return new {{operationIdCamelCase}}ActionBuilder();
}

public String getMethod() {
return "{{httpMethod}}";
}

public String getPath() {
return "{{path}}";
}

/**
* This method sends the HTTP-Request
*/
public void sendRequest(TestContext context) {
HttpClientRequestActionBuilder httpClientRequestActionBuilder = new HttpActionBuilder().client(httpClient).send()
.{{#lambda.lowercase}}{{httpMethod}}{{/lambda.lowercase}}(replacePathParams(ENDPOINT));

HttpMessageBuilderSupport messageBuilderSupport = httpClientRequestActionBuilder.getMessageBuilderSupport();
messageBuilderSupport.accept(responseAcceptType);

if (cookies != null) {
cookies.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
}

if (headers != null) {
headers.forEach((k, v) -> messageBuilderSupport.cookie(new Cookie(k, v)));
headers.forEach(messageBuilderSupport::header);
}

String bodyLog = "";
{{#isMultipart}}
MultiValueMap<String, Object> multiValues = new LinkedMultiValueMap<>();
{{#formParams}}
{{#required}}
if(StringUtils.isBlank({{paramName}})) {
throw new CitrusRuntimeException(String.format("Required attribute '%s' is not specified", "{{paramName}}"));
}
{{/required}}
{{#isBinary}}
if (StringUtils.isNotBlank({{paramName}})) {
multiValues.add("{{paramName}}", new ClassPathResource({{paramName}}));
bodyLog += {{paramName}}.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") +",";
}
{{/isBinary}}
{{^isBinary}}
if (StringUtils.isNotBlank({{paramName}})) {
// first try to load from resource
ClassPathResource resource = null;
try {
resource = new ClassPathResource({{paramName}});
}
catch(Exception ignore) {
// Use plain text instead of resource
}

if(resource != null && resource.exists()){
multiValues.add("{{paramName}}", resource);
} else {
multiValues.add("{{paramName}}", {{paramName}});
}
bodyLog += {{paramName}}.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") +",";
}
{{/isBinary}}
{{/formParams}}

bodyLog += "\";\"" + MediaType.MULTIPART_FORM_DATA_VALUE + "\"";
messageBuilderSupport.contentType(MediaType.MULTIPART_FORM_DATA_VALUE)
.body(multiValues);

{{/isMultipart}}
{{^isMultipart}}
String payload = null;
String payloadType = null;
if (StringUtils.isNotBlank(this.bodyFile)) {
try {
payload = FileUtils.readToString(Resources.create(this.bodyFile), FileUtils.getDefaultCharset());
} catch (IOException e) {
throw new CitrusRuntimeException("Failed to read payload resource", e);
}
payloadType = this.bodyContentType;
} else if (StringUtils.isNotBlank(this.bodyLiteral)) {
payload = this.bodyLiteral;
payloadType = this.bodyLiteralContentType;
}
String body = "";
String bodyType = "";
if(payload != null && payloadType != null) {
messageBuilderSupport.body(payload).contentType(payloadType);
body = context.replaceDynamicContentInString(payload);
bodyType = context.replaceDynamicContentInString(payloadType);
}

bodyLog = body.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" + bodyType + "\"";
{{/isMultipart}}

Map<String, String> queryParams = new HashMap<>();
{{#allParams}}{{#isQueryParam}}

if (StringUtils.isNotBlank(this.{{paramName}})) {
queryParams.put("{{baseName}}", context.replaceDynamicContentInString(this.{{paramName}}));
httpClientRequestActionBuilder.queryParam("{{baseName}}", this.{{paramName}});
}
{{/isQueryParam}}{{/allParams}}
String query = queryParams.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}"));
{{#authMethods}}{{#isBasic}}
{{/operation}}
{{/operations}}

if(basicUsername != null && basicPassword != null){
messageBuilderSupport.header("Authorization", "Basic " + Base64.getEncoder().encodeToString((context.replaceDynamicContentInString(basicUsername)+":"+context.replaceDynamicContentInString(basicPassword)).getBytes()));
}
{{/isBasic}}{{/authMethods}}
httpClientRequestActionBuilder.withReferenceResolver(context.getReferenceResolver());
httpClientRequestActionBuilder = customizeBuilder(INSTANCE, context, httpClientRequestActionBuilder);
{{#operations}}
{{#operation}}
public class {{operationIdCamelCase}}ActionBuilder extends TestApiClientRequestActionBuilder {
httpClientRequestActionBuilder.build().execute(context);
private static final String METHOD = "{{httpMethod}}";
coverageLogger.trace(coverageMarker, "{{operationId}};{{#lambda.uppercase}}{{httpMethod}}{{/lambda.uppercase}};\"" +
query.replace("\n", "\\n").replace("\r", "\\r").replace("\"", "\"\"") + "\";\"" +
bodyLog);
}
{{#queryParams}}
private static final String ENDPOINT = "{{path}}";
public void set{{#lambda.titlecase}}{{paramName}}{{/lambda.titlecase}}(String {{paramName}}) {
this.{{paramName}} = {{paramName}};
}
{{/queryParams}}
{{#pathParams}}
private static final String OPERATION_NAME = "{{operationId}}";
public void set{{#lambda.titlecase}}{{paramName}}{{/lambda.titlecase}}(String {{paramName}}) {
this.{{paramName}} = {{paramName}};
}
{{/pathParams}}
{{#isMultipart}}
{{#formParams}}
public AddPetActionBuilder() {
super(openApiSpecification, METHOD, ENDPOINT, OPERATION_NAME);
}

public void set{{#lambda.titlecase}}{{paramName}}{{/lambda.titlecase}}(String {{paramName}}) {
this.{{paramName}} = {{paramName}};
}
{{/formParams}}
{{/isMultipart}}
{{#authMethods}}{{#isBasic}}
{{#queryParams}}
public {{operationIdCamelCase}}ActionBuilder with{{#lambda.titlecase}}{{paramName}}{{/lambda.titlecase}}(String {{paramName}}) {
queryParam("{{paramName}}", {{paramName}});
return this;
}
{{/queryParams}}

public void setBasicUsername(String basicUsername) {
this.basicUsername = basicUsername;
}
// public AddPetActionBuilder withPet(Pet pet) {
// // TODO: fix this
// getMessageBuilderSupport().body(pet.toString());
// return this;
// }

public void setBasicPassword(String basicPassword) {
this.basicPassword = basicPassword;
}
{{/isBasic}}{{/authMethods}}
private String replacePathParams(String endpoint) {
{{#pathParams}}endpoint = endpoint.replace("{" + "{{baseName}}" + "}", {{paramName}});{{/pathParams}}
return endpoint;
}
}
{{/operation}}
{{/operations}}
}
{{/operations}}
}
Loading

0 comments on commit 9c38674

Please sign in to comment.