diff --git a/modules/openapi-generator/pom.xml b/modules/openapi-generator/pom.xml
index 112bcc4d980c..1f9db5a5febd 100644
--- a/modules/openapi-generator/pom.xml
+++ b/modules/openapi-generator/pom.xml
@@ -297,6 +297,11 @@
commons-lang3
${commons-lang.version}
+
+ org.apache.commons
+ commons-text
+ ${commons-text.version}
+
commons-cli
commons-cli
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenOperation.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenOperation.java
index 8d8e9cdc13e9..3404c933f3cc 100644
--- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenOperation.java
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenOperation.java
@@ -69,6 +69,10 @@ private static boolean nonempty(List> params) {
return params != null && params.size() > 0;
}
+ private static boolean nonempty(Map, ?> params) {
+ return params != null && params.size() > 0;
+ }
+
/**
* Check if there's at least one body parameter
*
@@ -186,6 +190,15 @@ public boolean getHasDefaultResponse() {
return responses.stream().filter(response -> response.isDefault).findFirst().isPresent();
}
+ /**
+ * Check if there's at least one vendor extension
+ *
+ * @return true if vendor extensions exists, false otherwise
+ */
+ public boolean getHasVendorExtensions() {
+ return nonempty(vendorExtensions);
+ }
+
/**
* Check if act as Restful index method
*
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/K6ClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/K6ClientCodegen.java
index 002084bc6165..df3739a0154b 100644
--- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/K6ClientCodegen.java
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/K6ClientCodegen.java
@@ -28,22 +28,27 @@
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
+import java.util.OptionalInt;
import java.util.Set;
+import java.util.TreeMap;
import java.util.TreeSet;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import com.google.common.collect.ImmutableMap;
-import org.apache.commons.lang3.StringEscapeUtils;
+import org.apache.commons.text.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.openapitools.codegen.CodegenConfig;
import org.openapitools.codegen.CodegenConstants;
import org.openapitools.codegen.CodegenModel;
+import org.openapitools.codegen.CodegenOperation;
import org.openapitools.codegen.CodegenParameter;
import org.openapitools.codegen.CodegenProperty;
import org.openapitools.codegen.CodegenResponse;
@@ -56,10 +61,13 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
import com.samskivert.mustache.Mustache;
import com.samskivert.mustache.Mustache.Lambda;
import com.samskivert.mustache.Template;
+import io.swagger.v3.core.util.Json;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.PathItem;
@@ -73,6 +81,24 @@
public class K6ClientCodegen extends DefaultCodegen implements CodegenConfig {
+ // K6 vendor extension - operation grouping - group operations and define their
+ // ordering, to allow for e.g. scenario testing
+ private static final String X_OPERATION_GROUPING = "x-k6-openapi-operation-grouping";
+
+ // K6 vendor extension - operation response - for now, allows to hide given
+ // operation response, so that in case of multiple 2xx responses, generated
+ // script checks only against e.g. code 200 responses
+ private static final String X_OPERATION_RESPONSE = "x-k6-openapi-operation-response";
+ private static final String X_OPERATION_RESPONSE_HIDE = "hide";
+
+ // K6 vendor extension - extract data from operation - allows to specify path to
+ // value in body of response which should be extracted and assigned to variable
+ // for later use by other operations
+ private static final String X_OPERATION_DATAEXTRACT = "x-k6-openapi-operation-dataextract";
+ private static final String X_OPERATION_DATAEXTRACT_OPERATION_ID = "operationId"; // denotes ID of operation whose response body contains value to be extracted
+ private static final String X_OPERATION_DATAEXTRACT_VALUE_PATH = "valuePath"; // denotes path to value in body of response which should be extracted
+ private static final String X_OPERATION_DATAEXTRACT_PARAMETER_NAME = "parameterName"; // denotes name of parameter to which extracted value should be assigned
+
public K6ClientCodegen() {
super();
@@ -86,6 +112,7 @@ static class Parameter {
String key;
Object value;
boolean hasExample;
+ boolean initialize;
public Parameter(String key, Object value) {
this.key = key;
@@ -98,6 +125,11 @@ public Parameter(String key, Object exampleValue, boolean hasExample) {
this.hasExample = hasExample;
}
+ public Parameter(String key, boolean initialize) {
+ this.key = key;
+ this.initialize = initialize;
+ }
+
@Override
public int hashCode() {
return key.hashCode();
@@ -110,10 +142,35 @@ public boolean equals(Object obj) {
if (obj == null || getClass() != obj.getClass())
return false;
Parameter p = (Parameter) obj;
- return key.equals(p.key) && value.equals(p.value) && hasExample == p.hasExample;
+ return key.equals(p.key) && value.equals(p.value) && hasExample == p.hasExample
+ && initialize == p.initialize;
}
}
-
+
+ // Stores information specified in `X_OPERATION_GROUPING` K6 vendor extension
+ static class OperationGrouping {
+ String groupName;
+ int order;
+
+ public OperationGrouping(String groupName, int order) {
+ this.groupName = groupName;
+ this.order = order;
+ }
+ }
+
+ // Stores information specified in `X_OPERATION_DATAEXTRACT` K6 vendor extension
+ static class DataExtractSubstituteParameter {
+ String operationId;
+ String valuePath;
+ String paramName;
+
+ public DataExtractSubstituteParameter(String operationId, String valuePath, String paramName) {
+ this.operationId = operationId;
+ this.valuePath = valuePath;
+ this.paramName = paramName;
+ }
+ }
+
static class ParameterValueLambda implements Mustache.Lambda {
private static final String NO_EXAMPLE_PARAM_VALUE_PREFIX = "TODO_EDIT_THE_";
@@ -123,7 +180,7 @@ public void execute(Template.Fragment fragment, Writer writer) throws IOExceptio
// default used if no example is provided
String noExampleParamValue = String.join("",
quoteExample(
- String.join("", NO_EXAMPLE_PARAM_VALUE_PREFIX, fragment.execute())),
+ String.join("", NO_EXAMPLE_PARAM_VALUE_PREFIX, fragment.execute())),
";",
" // specify value as there is no example value for this parameter in OpenAPI spec");
@@ -138,10 +195,10 @@ public void execute(Template.Fragment fragment, Writer writer) throws IOExceptio
@SuppressWarnings("unchecked")
Set exampleValues = ((Map) rawValue).values().stream()
- .map(x -> quoteExample(
- StringEscapeUtils.escapeEcmaScript(
- String.valueOf(x.getValue()))))
- .collect(Collectors.toCollection(() -> new TreeSet()));
+ .map(x -> quoteExample(
+ StringEscapeUtils.escapeEcmaScript(
+ String.valueOf(x.getValue()))))
+ .collect(Collectors.toCollection(() -> new TreeSet()));
if (!exampleValues.isEmpty()) {
@@ -160,11 +217,20 @@ public void execute(Template.Fragment fragment, Writer writer) throws IOExceptio
quoteExample(
StringEscapeUtils.escapeEcmaScript(
String.valueOf(
- ((K6ClientCodegen.Parameter) fragment.context()).value))),
+ ((K6ClientCodegen.Parameter) fragment.context()).value))),
";",
" // extracted from 'example' field defined at the parameter level of OpenAPI spec"));
}
+ // param needs to be initialized for subsequent data extraction - see `X_OPERATION_DATAEXTRACT` K6 vendor extension
+ } else if (fragment.context() instanceof K6ClientCodegen.Parameter
+ && ((K6ClientCodegen.Parameter) fragment.context()).initialize) {
+
+ writer.write(String.join("",
+ "null",
+ ";",
+ " // parameter initialized for subsequent data extraction"));
+
} else {
writer.write(noExampleParamValue);
}
@@ -231,25 +297,33 @@ public k6Check(Integer status, String description) {
static class HTTPRequest {
String method;
+ boolean isDelete;
String path;
@Nullable
List query;
@Nullable
HTTPBody body;
+ boolean hasBodyExample;
@Nullable
HTTPParameters params;
@Nullable
List k6Checks;
+ @Nullable
+ DataExtractSubstituteParameter dataExtract;
public HTTPRequest(String method, String path, @Nullable List query, @Nullable HTTPBody body,
- @Nullable HTTPParameters params, @Nullable List k6Checks) {
+ boolean hasBodyExample, @Nullable HTTPParameters params, @Nullable List k6Checks,
+ DataExtractSubstituteParameter dataExtract) {
// NOTE: https://k6.io/docs/javascript-api/k6-http/del-url-body-params
this.method = method.equals("delete") ? "del" : method;
+ this.isDelete = method.equals("delete") ? true : false;
this.path = path;
this.query = query;
this.body = body;
+ this.hasBodyExample = hasBodyExample;
this.params = params;
this.k6Checks = k6Checks;
+ this.dataExtract = dataExtract;
}
}
@@ -257,11 +331,26 @@ static public class HTTPRequestGroup {
String groupName;
Set variables; // query and path parameters
List requests;
+ private Map requestsMap;
- public HTTPRequestGroup(String groupName, Set variables, List requests) {
+ public HTTPRequestGroup(String groupName, Set variables, Map requestsMap) {
this.groupName = groupName;
this.variables = variables;
- this.requests = requests;
+ this.requestsMap = requestsMap;
+ this.requests = sortRequests(requestsMap);
+ }
+
+ private void addRequests(Map moreRequests) {
+ this.requestsMap.putAll(moreRequests);
+ this.requests = sortRequests(this.requestsMap);
+ }
+
+ private void addVariables(Set moreVariables) {
+ this.variables.addAll(moreVariables);
+ }
+
+ private List sortRequests(Map requestsMap) {
+ return new ArrayList<>(new TreeMap(requestsMap).values());
}
}
@@ -398,14 +487,21 @@ public void preprocessOpenAPI(OpenAPI openAPI) {
}
additionalProperties.put(BASE_URL, baseURL);
- List requestGroups = new ArrayList<>();
+ // if data is to be extracted from any of the operations' responses, this has to
+ // be known prior to executing processing of OpenAPI spec further down
+ Map dataExtractSubstituteParams = getDataExtractSubstituteParameters(
+ openAPI);
+
+ Map requestGroups = new HashMap<>();
Set extraParameters = new HashSet<>();
Map> pathVariables = new HashMap<>();
for (String path : openAPI.getPaths().keySet()) {
- List requests = new ArrayList<>();
+ Map requests = new HashMap<>();
Set variables = new HashSet<>();
+ String groupName = path;
+
for (Map.Entry methodOperation : openAPI.getPaths().get(path).
readOperationsMap().entrySet()) {
List httpParams = new ArrayList<>();
@@ -416,11 +512,33 @@ public void preprocessOpenAPI(OpenAPI openAPI) {
final Operation operation = methodOperation.getValue();
final PathItem.HttpMethod method = methodOperation.getKey();
+ OptionalInt operationGroupingOrder = OptionalInt.empty();
+
+ final CodegenOperation cgOperation = super.fromOperation(path, method.name(), operation, null);
+
+ String operationId = operation.getOperationId();
+
+ boolean hasRequestBodyExample = false;
+
+ // optionally group and order operations - see `X_OPERATION_GROUPING` K6 vendor
+ // extension
+ Optional operationGrouping = extractOperationGrouping(cgOperation);
+ if (operationGrouping.isPresent()) {
+ groupName = operationGrouping.get().groupName;
+ operationGroupingOrder = OptionalInt.of(operationGrouping.get().order);
+ }
for (Map.Entry resp : operation.getResponses().entrySet()) {
String statusData = resp.getKey().equals("default") ? "200" : resp.getKey();
+
+ // optionally hide given response - see `X_OPERATION_RESPONSE` K6 vendor
+ // extension
+ // i.e. in case of multiple 2xx responses, generated script checks only against
+ // e.g. code 200 responses
+ boolean hideOperationResponse = shouldHideOperationResponse(resp.getValue());
+
int status = Integer.parseInt(statusData);
- if (status >= 200 && status < 300) {
+ if (!hideOperationResponse && (status >= 200 && status < 300)) {
k6Checks.add(new k6Check(status, resp.getValue().getDescription()));
}
}
@@ -436,6 +554,12 @@ public void preprocessOpenAPI(OpenAPI openAPI) {
RequestBody requestBody = ModelUtils.getReferencedRequestBody(openAPI, operation.getRequestBody());
+ // extract request body example, if present
+ hasRequestBodyExample = hasRequestBodyExample(requestBody, contentTypeValue);
+ if (hasRequestBodyExample) {
+ extractRequestBodyExample(requestBody, contentTypeValue, bodyOrFormParams);
+ }
+
for (Map.Entry responseEntry : operation.getResponses().entrySet()) {
CodegenResponse r = fromResponse(responseEntry.getKey(), responseEntry.getValue());
if (r.baseType != null &&
@@ -445,30 +569,34 @@ public void preprocessOpenAPI(OpenAPI openAPI) {
}
}
- List formParameters = fromRequestBodyToFormParameters(requestBody, imports);
- for (CodegenParameter parameter : formParameters) {
- String reference = "";
- if (parameter.isModel) {
- Schema nestedSchema = ModelUtils.getSchema(openAPI, parameter.baseType);
- CodegenModel model = fromModel(parameter.paramName, nestedSchema);
- reference = generateNestedModelTemplate(model);
- if (parameter.dataType.equals("List")) {
- reference = "[" + reference + "]";
+ // if we have at least one request body example, we do not need to construct these dummies
+ if (!hasRequestBodyExample) {
+ List formParameters = fromRequestBodyToFormParameters(requestBody, imports);
+ for (CodegenParameter parameter : formParameters) {
+ String reference = "";
+ if (parameter.isModel) {
+ Schema nestedSchema = ModelUtils.getSchema(openAPI, parameter.baseType);
+ CodegenModel model = fromModel(parameter.paramName, nestedSchema);
+ reference = generateNestedModelTemplate(model);
+ if (parameter.dataType.equals("List")) {
+ reference = "[" + reference + "]";
+ }
}
- }
- Parameter k6Parameter;
- if (parameter.dataType.equals("File")) {
- k6Parameter = new Parameter(parameter.paramName,
- "http.file(open(\"/path/to/file.bin\", \"b\"), \"test.bin\")");
- } else {
- k6Parameter = new Parameter(parameter.paramName, !reference.isEmpty() ? reference
- : getDoubleQuotedString(parameter.dataType.toLowerCase(Locale.ROOT)));
- }
+ Parameter k6Parameter;
+ if (parameter.dataType.equals("File")) {
+ k6Parameter = new Parameter(parameter.paramName,
+ "http.file(open(\"/path/to/file.bin\", \"b\"), \"test.bin\")");
+ } else {
+ k6Parameter = new Parameter(parameter.paramName, !reference.isEmpty() ? reference
+ : getDoubleQuotedString(parameter.dataType.toLowerCase(Locale.ROOT)));
+ }
- bodyOrFormParams.add(k6Parameter);
+ bodyOrFormParams.add(k6Parameter);
+ }
}
}
+
String accepts = getAccept(openAPI, operation);
String responseType = getDoubleQuotedString(accepts);
@@ -510,29 +638,43 @@ public void preprocessOpenAPI(OpenAPI openAPI) {
}
- pathVariables.put(path, variables);
+ pathVariables.put(groupName, variables);
final HTTPParameters params = new HTTPParameters(null, null, httpParams, null, null, null, null, null,
responseType.length() > 0 ? responseType : null);
assert params.headers != null;
- requests.add(new HTTPRequest(method.toString().toLowerCase(Locale.ROOT), path,
+
+ // check if data needs to be extracted from response of this operation
+ Optional dataExtract = getDataExtractSubstituteParameter(
+ dataExtractSubstituteParams, operationId);
+
+ // calculate order for this current request
+ Integer requestOrder = calculateRequestOrder(operationGroupingOrder, requests.size());
+
+ requests.put(requestOrder, new HTTPRequest(method.toString().toLowerCase(Locale.ROOT), path,
queryParams.size() > 0 ? queryParams : null,
- bodyOrFormParams.size() > 0 ? new HTTPBody(bodyOrFormParams) : null,
- params.headers.size() > 0 ? params : null, k6Checks.size() > 0 ? k6Checks : null));
+ bodyOrFormParams.size() > 0 ? new HTTPBody(bodyOrFormParams) : null, hasRequestBodyExample,
+ params.headers.size() > 0 ? params : null, k6Checks.size() > 0 ? k6Checks : null,
+ dataExtract.isPresent() ? dataExtract.get() : null));
}
- requestGroups.add(new HTTPRequestGroup(path, pathVariables.get(path), requests));
+
+ addOrUpdateRequestGroup(requestGroups, groupName, pathVariables.get(groupName), requests);
}
- for (HTTPRequestGroup requestGroup : requestGroups) {
+ for (HTTPRequestGroup requestGroup : requestGroups.values()) {
for (HTTPRequest request : requestGroup.requests) {
if (request.path.contains("/{")) {
request.path = request.path.replace("/{", "/${");
}
}
+
+ // any variables not defined yet but used for subsequent data extraction must be
+ // initialized
+ initializeDataExtractSubstituteParameters(dataExtractSubstituteParams, requestGroup);
}
- additionalProperties.put("requestGroups", requestGroups);
+ additionalProperties.put("requestGroups", requestGroups.values());
additionalProperties.put("extra", extraParameters);
for (String[] supportingTemplateFile : JAVASCRIPT_SUPPORTING_FILES) {
@@ -744,9 +886,282 @@ private static String getAccept(OpenAPI openAPI, Operation operation) {
return accepts;
}
-
+
@Override
protected ImmutableMap.Builder addMustacheLambdas() {
return super.addMustacheLambdas().put("handleParamValue", new ParameterValueLambda());
}
+
+ /**
+ * We're iterating over paths but grouping requests across paths, therefore
+ * these need to be aggregated.
+ *
+ * @param requestGroups
+ * @param groupName
+ * @param variables
+ * @param requests
+ */
+ private void addOrUpdateRequestGroup(Map requestGroups, String groupName,
+ Set variables, Map requests) {
+ if (requestGroups.containsKey(groupName)) {
+ HTTPRequestGroup existingHTTPRequestGroup = requestGroups.get(groupName);
+ existingHTTPRequestGroup.addRequests(requests);
+ existingHTTPRequestGroup.addVariables(variables);
+ } else {
+ requestGroups.put(groupName, new HTTPRequestGroup(groupName, variables, requests));
+ }
+ }
+
+ /**
+ * If `X_OPERATION_DATAEXTRACT` K6 vendor extension is present, extract info
+ * from it.
+ *
+ * @param openAPI
+ * @return
+ */
+ private Map getDataExtractSubstituteParameters(OpenAPI openAPI) {
+ Map dataExtractSubstituteParams = new HashMap<>();
+
+ for (String path : openAPI.getPaths().keySet()) {
+ for (Map.Entry methodOperation : openAPI.getPaths().get(path)
+ .readOperationsMap().entrySet()) {
+
+ final PathItem.HttpMethod method = methodOperation.getKey();
+ final Operation operation = methodOperation.getValue();
+ final CodegenOperation cgOperation = super.fromOperation(path, method.name(), operation, null);
+
+ if (cgOperation.getHasVendorExtensions()
+ && cgOperation.vendorExtensions.containsKey(X_OPERATION_DATAEXTRACT)
+ && cgOperation.vendorExtensions.get(X_OPERATION_DATAEXTRACT) instanceof java.util.Map) {
+
+ Optional dataExtractSubstituteParameter = getDataExtractSubstituteParameter(
+ (Map, ?>) cgOperation.vendorExtensions.get(X_OPERATION_DATAEXTRACT));
+
+ // TODO: add support for extracting data for multiple params
+ if (dataExtractSubstituteParameter.isPresent()) {
+ dataExtractSubstituteParams.put(dataExtractSubstituteParameter.get().operationId,
+ dataExtractSubstituteParameter.get());
+ }
+ }
+
+ }
+ }
+
+ return dataExtractSubstituteParams;
+ }
+
+ /**
+ * Optionally, retrieve information specified in the `X_OPERATION_DATAEXTRACT`
+ * K6 vendor extension
+ *
+ * @param xOperationDataExtractProperties
+ * @return optional as only returned if all required info is present
+ */
+ private Optional getDataExtractSubstituteParameter(
+ Map, ?> xOperationDataExtractProperties) {
+
+ Optional operationId = Optional.empty();
+ Optional valuePath = Optional.empty();
+ Optional parameterName = Optional.empty();
+
+ for (Map.Entry, ?> xOperationDataExtractPropertiesEntry : xOperationDataExtractProperties.entrySet()) {
+
+ switch (String.valueOf(xOperationDataExtractPropertiesEntry.getKey())) {
+ case X_OPERATION_DATAEXTRACT_OPERATION_ID:
+ operationId = Optional.of(String.valueOf(xOperationDataExtractPropertiesEntry.getValue()));
+ continue;
+
+ case X_OPERATION_DATAEXTRACT_VALUE_PATH:
+ valuePath = Optional.of(String.valueOf(xOperationDataExtractPropertiesEntry.getValue()));
+ continue;
+
+ case X_OPERATION_DATAEXTRACT_PARAMETER_NAME:
+ parameterName = Optional.of(String.valueOf(xOperationDataExtractPropertiesEntry.getValue()));
+ continue;
+ }
+ }
+
+ if (operationId.isPresent() && valuePath.isPresent() && parameterName.isPresent()) {
+ return Optional
+ .of(new DataExtractSubstituteParameter(operationId.get(), valuePath.get(), parameterName.get()));
+
+ } else {
+ return Optional.empty();
+ }
+ }
+
+ /**
+ * Optionally, retrieve data extraction properties for given operation
+ *
+ * @param dataExtractSubstituteParams
+ * @param operationId
+ * @return optional as only returned if present for given operation
+ */
+ private Optional getDataExtractSubstituteParameter(
+ Map dataExtractSubstituteParams, String operationId) {
+
+ return (!dataExtractSubstituteParams.isEmpty() && dataExtractSubstituteParams.containsKey(operationId))
+ ? Optional.of(dataExtractSubstituteParams.get(operationId))
+ : Optional.empty();
+ }
+
+ /**
+ * Optionally, retrieve information specified in the `X_OPERATION_GROUPING` K6
+ * vendor extension
+ *
+ * @param cgOperation
+ * @return optional as only returned if required info is present
+ */
+ private Optional extractOperationGrouping(CodegenOperation cgOperation) {
+ Optional operationGrouping = Optional.empty();
+
+ if (cgOperation.getHasVendorExtensions() && cgOperation.vendorExtensions.containsKey(X_OPERATION_GROUPING)
+ && cgOperation.vendorExtensions.get(X_OPERATION_GROUPING) instanceof java.util.Map) {
+
+ Map.Entry, ?> operationGroupingEntry = ((Map, ?>) cgOperation.vendorExtensions
+ .get(X_OPERATION_GROUPING)).entrySet().stream().findFirst().get();
+
+ return Optional.of(new OperationGrouping(String.valueOf(operationGroupingEntry.getKey()),
+ Integer.parseInt(String.valueOf(operationGroupingEntry.getValue()))));
+ }
+
+ return operationGrouping;
+ }
+
+ /**
+ * If `X_OPERATION_RESPONSE` K6 vendor extension is present, check if given
+ * operation response should be hidden.
+ *
+ * @param resp
+ * @return true if should be hidden, false otherwise
+ */
+ private boolean shouldHideOperationResponse(ApiResponse resp) {
+ boolean hideOperationResponse = false;
+
+ if (Objects.nonNull(resp.getExtensions()) && !resp.getExtensions().isEmpty()
+ && resp.getExtensions().containsKey(X_OPERATION_RESPONSE)) {
+
+ Map, ?> respExtensions = (Map, ?>) resp.getExtensions().get(X_OPERATION_RESPONSE);
+
+ for (Map.Entry, ?> respExtensionEntry : respExtensions.entrySet()) {
+
+ switch (String.valueOf(respExtensionEntry.getKey())) {
+ case X_OPERATION_RESPONSE_HIDE:
+ hideOperationResponse = Boolean.valueOf(respExtensionEntry.getValue().toString());
+ continue;
+ }
+ }
+ }
+
+ return hideOperationResponse;
+ }
+
+ /**
+ * Check if example is present for given request body and content type.
+ *
+ * @param requestBody
+ * @param contentTypeValue
+ * @return true if present, false otherwise
+ */
+ private boolean hasRequestBodyExample(RequestBody requestBody, String contentTypeValue) {
+ return (Objects.nonNull(requestBody.getContent()) && requestBody.getContent().containsKey(contentTypeValue)
+ && Objects.nonNull(requestBody.getContent().get(contentTypeValue).getExamples())
+ && !requestBody.getContent().get(contentTypeValue).getExamples().isEmpty());
+ }
+
+ /**
+ * Extract example for given request body.
+ *
+ * @param requestBody
+ * @param contentTypeValue
+ * @param bodyOrFormParams
+ */
+ private void extractRequestBodyExample(RequestBody requestBody, String contentTypeValue,
+ List bodyOrFormParams) {
+
+ Optional> requestBodyExampleEntry = requestBody.getContent().get(contentTypeValue)
+ .getExamples().entrySet().stream().findFirst();
+
+ if (requestBodyExampleEntry.isPresent()) {
+
+ Example requestBodyExample = requestBodyExampleEntry.get().getValue();
+
+ try {
+ JsonNode requestBodyExampleValueJsonNode = Json.mapper()
+ .readTree(String.valueOf(requestBodyExample.getValue()));
+
+ Iterator> fields = requestBodyExampleValueJsonNode.fields();
+ while (fields.hasNext()) {
+ Map.Entry fieldsEntry = fields.next();
+
+ JsonNode exampleValueAsJsonNode = fieldsEntry.getValue();
+
+ Parameter k6Parameter = new Parameter(fieldsEntry.getKey(),
+ exampleValueAsJsonNode.isNumber() ? exampleValueAsJsonNode.asText()
+ : exampleValueAsJsonNode.toString());
+
+ bodyOrFormParams.add(k6Parameter);
+ }
+
+ } catch (JsonProcessingException e) {
+ LOGGER.error(e.getMessage(), e);
+ }
+ }
+ }
+
+ /**
+ * Calculate order for this current request
+ *
+ * @param operationGroupingOrder
+ * @param requests
+ * @return request order
+ */
+ private Integer calculateRequestOrder(OptionalInt operationGroupingOrder, int requestsSize) {
+ int requestOrder = 0;
+
+ if (operationGroupingOrder.isPresent()) {
+ requestOrder = Integer.valueOf(operationGroupingOrder.getAsInt() - 1);
+
+ } else {
+ switch (requestsSize) {
+ case 0:
+ case 1:
+ requestOrder = requestsSize;
+ break;
+
+ default:
+ requestOrder = (requestsSize - 1);
+ break;
+ }
+ }
+
+ return Integer.valueOf(requestOrder);
+ }
+
+ //
+ /**
+ * Any variables not defined yet but used for subsequent data extraction must be
+ * initialized
+ *
+ * @param dataExtractSubstituteParams
+ * @param requestGroup
+ */
+ private void initializeDataExtractSubstituteParameters(
+ Map dataExtractSubstituteParams, HTTPRequestGroup requestGroup) {
+
+ if (!dataExtractSubstituteParams.isEmpty()) {
+ List existingVariablesNames = requestGroup.variables.stream().map(v -> v.key)
+ .collect(Collectors.toList());
+
+ Set initializeVariables = dataExtractSubstituteParams.values().stream()
+ .filter(p -> !existingVariablesNames.contains(toVarName(p.paramName))).collect(Collectors.toSet());
+
+ if (!initializeVariables.isEmpty()) {
+ for (DataExtractSubstituteParameter initializeVariable : initializeVariables) {
+ requestGroup.variables.add(new Parameter(toVarName(initializeVariable.paramName), true));
+ }
+ }
+ }
+ }
+
}
diff --git a/modules/openapi-generator/src/main/resources/k6/script.mustache b/modules/openapi-generator/src/main/resources/k6/script.mustache
index cac3381de01f..4138e10a0294 100644
--- a/modules/openapi-generator/src/main/resources/k6/script.mustache
+++ b/modules/openapi-generator/src/main/resources/k6/script.mustache
@@ -19,47 +19,52 @@ export default function() {
let {{{key}}} = {{#lambda.handleParamValue}}{{value}}{{/lambda.handleParamValue}}
{{/variables}}
{{#requests}}
- {{#-first}}
- let url = BASE_URL + `{{{path}}}{{=<% %>=}}<%#query%><%#-first%>?<%/-first%><%& key%>=<%& value%><%^-last%>&<%/-last%><%/query%><%={{ }}=%>`;
+
// Request No. {{-index}}
- {{#body}}
- // TODO: edit the parameters of the request body.
- let body = {{#body}}{{=<% %>=}}{<%#parameters%>"<%& key%>": <%& value%><%^-last%>, <%/-last%><%/parameters%>}<%={{ }}=%>{{/body}};
- {{/body}}
- {{#params}}
- let params = {{#params}}{{=<% %>=}}{headers: {<%# headers%>"<%& key%>": <%& value%><%^-last%>, <%/-last%><%/headers%><%#responseType%>, "Accept": <%& responseType%><%/responseType%>}<%# auth%>, auth: "<%& auth%>"<%/auth%>}<%={{ }}=%>{{/params}};
- {{/params}}
- let request = http.{{method}}(url{{#body}}, body{{/body}}{{#params}}, params{{/params}});
- {{#k6Checks}}
- {{=<% %>=}}
- check(request, {
+ {
+ let url = BASE_URL + `{{{path}}}{{=<% %>=}}<%#query%><%#-first%>?<%/-first%><%& key%>=<%& value%><%^-last%>&<%/-last%><%/query%><%={{ }}=%>`;
+ {{#body}}
+ {{^hasBodyExample}}
+ // TODO: edit the parameters of the request body.
+ {{/hasBodyExample}}
+ let body = {{#body}}{{=<% %>=}}{<%#parameters%>"<%& key%>": <%& value%><%^-last%>, <%/-last%><%/parameters%>}<%={{ }}=%>{{/body}};
+ {{/body}}
+
+ {{#params}}
+ let params = {{#params}}{{=<% %>=}}{headers: {<%# headers%>"<%& key%>": <%& value%><%^-last%>, <%/-last%><%/headers%><%#responseType%>, "Accept": <%& responseType%><%/responseType%>}<%# auth%>, auth: "<%& auth%>"<%/auth%>}<%={{ }}=%>{{/params}};
+ {{/params}}
+
+ {{#isDelete}}
+ {{#params}}
+ // this is a DELETE method request - if params are also set, empty body must be passed
+ let request = http.{{method}}(url, {} {{#params}}, params{{/params}});
+ {{/params}}
+ {{^params}}
+ let request = http.{{method}}(url);
+ {{/params}}
+ {{/isDelete}}
+
+ {{^isDelete}}
+ let request = http.{{method}}(url{{#body}}, JSON.stringify(body){{/body}}{{#params}}, params{{/params}});
+ {{/isDelete}}
+
+ {{#k6Checks}}
+ {{=<% %>=}}
+ check(request, {
"<%& description%>": (r) => r.status === <%& status%>
- });
- <%={{ }}=%>
- {{/k6Checks}}
- {{/-first}}
- {{^-first}}
- // Request No. {{-index}}
- {{#body}}
- // TODO: edit the parameters of the request body.
- body = {{#body}}{{=<% %>=}}{<%#parameters%>"<%& key%>": <%& value%><%^-last%>, <%/-last%><%/parameters%>}<%={{ }}=%>{{/body}};
- {{/body}}
- {{#params}}
- params = {{#params}}{{=<% %>=}}{headers: {<%# headers%>"<%& key%>": <%& value%><%^-last%>, <%/-last%><%/headers%>}<%# auth%>, auth: "<%& auth%>"<%/auth%>}<%={{ }}=%>{{/params}};
- {{/params}}
- request = http.{{method}}(url{{#body}}, body{{/body}}{{#params}}, params{{/params}});
- {{#k6Checks}}
- {{=<% %>=}}
- check(request, {
- "<%& description%>": (r) => r.status === <%& status%>
- });
- <%={{ }}=%>
- {{/k6Checks}}
- {{/-first}}
- sleep(SLEEP_DURATION);
- {{^-last}}
-
- {{/-last}}
+ });
+ <%={{ }}=%>
+ {{/k6Checks}}
+
+ {{#dataExtract}}
+ {{{paramName}}} = JSON.parse(request.body).{{{valuePath}}}; // extract data for subsequent use
+ {{/dataExtract}}
+
+ {{^-last}}
+ sleep(SLEEP_DURATION);
+ {{/-last}}
+ }
+
{{/requests}}
});
{{/requestGroups}}
diff --git a/pom.xml b/pom.xml
index bca552e102dc..785c15a82e63 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1544,6 +1544,7 @@
1.4
2.11.0
3.12.0
+ 1.9
1.3.0
1.0.2
4.9.10
diff --git a/samples/client/petstore/k6/.openapi-generator/FILES b/samples/client/petstore/k6/.openapi-generator/FILES
new file mode 100644
index 000000000000..343cd1bd1f83
--- /dev/null
+++ b/samples/client/petstore/k6/.openapi-generator/FILES
@@ -0,0 +1,2 @@
+README.md
+script.js
diff --git a/samples/client/petstore/k6/script.js b/samples/client/petstore/k6/script.js
index 6988d6417da5..966e67a6132a 100644
--- a/samples/client/petstore/k6/script.js
+++ b/samples/client/petstore/k6/script.js
@@ -23,182 +23,335 @@ let apiKey = "TODO_EDIT_THE_API_KEY";
export default function() {
group("/pet", () => {
- let url = BASE_URL + `/pet`;
+
// Request No. 1
- // TODO: edit the parameters of the request body.
- let body = {"id": "long", "category": {"id": "long", "name": "string"}, "name": "string", "photoUrls": "list", "tags": [{"id": "long", "name": "string"}], "status": "string"};
- let params = {headers: {"Content-Type": "application/json", "Accept": "application/json"}};
- let request = http.put(url, body, params);
- sleep(SLEEP_DURATION);
-
+ {
+ let url = BASE_URL + `/pet`;
+ // TODO: edit the parameters of the request body.
+ let body = {"id": "long", "category": {"id": "long", "name": "string"}, "name": "string", "photoUrls": "list", "tags": "list", "status": "string"};
+
+ let params = {headers: {"Content-Type": "application/json", "Accept": "application/json"}};
+
+
+ let request = http.put(url, JSON.stringify(body), params);
+
+
+
+ sleep(SLEEP_DURATION);
+ }
+
+
// Request No. 2
- // TODO: edit the parameters of the request body.
- body = {"id": "long", "category": {"id": "long", "name": "string"}, "name": "string", "photoUrls": "list", "tags": [{"id": "long", "name": "string"}], "status": "string"};
- params = {headers: {"Content-Type": "application/json"}};
- request = http.post(url, body, params);
- sleep(SLEEP_DURATION);
+ {
+ let url = BASE_URL + `/pet`;
+ // TODO: edit the parameters of the request body.
+ let body = {"id": "long", "category": {"id": "long", "name": "string"}, "name": "string", "photoUrls": "list", "tags": "list", "status": "string"};
+
+ let params = {headers: {"Content-Type": "application/json", "Accept": "application/json"}};
+
+
+ let request = http.post(url, JSON.stringify(body), params);
+
+
+
+ }
+
});
- group("/pet/findByStatus", () => {
- let status = 'TODO_EDIT_THE_STATUS'; // specify value as there is no example value for this parameter in OpenAPI spec
- let url = BASE_URL + `/pet/findByStatus?status=${status}`;
+ group("/user/{username}", () => {
+ let username = 'TODO_EDIT_THE_USERNAME'; // specify value as there is no example value for this parameter in OpenAPI spec
+
// Request No. 1
- let request = http.get(url);
- check(request, {
+ {
+ let url = BASE_URL + `/user/${username}`;
+
+
+
+ let request = http.get(url);
+
+ check(request, {
"successful operation": (r) => r.status === 200
- });
- sleep(SLEEP_DURATION);
+ });
+
+
+ sleep(SLEEP_DURATION);
+ }
+
+
+ // Request No. 2
+ {
+ let url = BASE_URL + `/user/${username}`;
+
+
+ let request = http.del(url);
+
+
+
+
+ }
+
});
- group("/pet/findByTags", () => {
- let tags = 'TODO_EDIT_THE_TAGS'; // specify value as there is no example value for this parameter in OpenAPI spec
- let url = BASE_URL + `/pet/findByTags?tags=${tags}`;
+ group("/pet/findByStatus", () => {
+ let status = 'TODO_EDIT_THE_STATUS'; // specify value as there is no example value for this parameter in OpenAPI spec
+
// Request No. 1
- let request = http.get(url);
- check(request, {
+ {
+ let url = BASE_URL + `/pet/findByStatus?status=${status}`;
+
+
+
+ let request = http.get(url);
+
+ check(request, {
"successful operation": (r) => r.status === 200
- });
- sleep(SLEEP_DURATION);
+ });
+
+
+ }
+
});
- group("/pet/{petId}", () => {
- let petId = 'TODO_EDIT_THE_PETID'; // specify value as there is no example value for this parameter in OpenAPI spec
- let url = BASE_URL + `/pet/${petId}`;
+ group("/user/createWithList", () => {
+
// Request No. 1
- let request = http.get(url);
- check(request, {
+ {
+ let url = BASE_URL + `/user/createWithList`;
+
+ let params = {headers: {"Content-Type": "application/json", "Accept": "application/json"}};
+
+
+ let request = http.post(url, params);
+
+ check(request, {
"successful operation": (r) => r.status === 200
- });
- sleep(SLEEP_DURATION);
-
- // Request No. 2
- // TODO: edit the parameters of the request body.
- body = {"name": "string", "status": "string"};
- params = {headers: {"Content-Type": "application/x-www-form-urlencoded"}};
- request = http.post(url, body, params);
- sleep(SLEEP_DURATION);
-
- // Request No. 3
- params = {headers: {"api_key": `${apiKey}`}};
- request = http.del(url, params);
- sleep(SLEEP_DURATION);
+ });
+
+
+ }
+
});
group("/pet/{petId}/uploadImage", () => {
let petId = 'TODO_EDIT_THE_PETID'; // specify value as there is no example value for this parameter in OpenAPI spec
- let url = BASE_URL + `/pet/${petId}/uploadImage`;
+
// Request No. 1
- // TODO: edit the parameters of the request body.
- let body = {"additionalMetadata": "string", "file": http.file(open("/path/to/file.bin", "b"), "test.bin")};
- let params = {headers: {"Content-Type": "multipart/form-data", "Accept": "application/json"}};
- let request = http.post(url, body, params);
- check(request, {
+ {
+ let url = BASE_URL + `/pet/${petId}/uploadImage`;
+ // TODO: edit the parameters of the request body.
+ let body = {"additionalMetadata": "string", "file": http.file(open("/path/to/file.bin", "b"), "test.bin")};
+
+ let params = {headers: {"Content-Type": "multipart/form-data", "Accept": "application/json"}};
+
+
+ let request = http.post(url, JSON.stringify(body), params);
+
+ check(request, {
"successful operation": (r) => r.status === 200
- });
- sleep(SLEEP_DURATION);
+ });
+
+
+ }
+
});
group("/store/inventory", () => {
- let url = BASE_URL + `/store/inventory`;
+
// Request No. 1
- let request = http.get(url);
- check(request, {
+ {
+ let url = BASE_URL + `/store/inventory`;
+
+
+
+ let request = http.get(url);
+
+ check(request, {
"successful operation": (r) => r.status === 200
- });
- sleep(SLEEP_DURATION);
+ });
+
+
+ }
+
});
- group("/store/order", () => {
- let url = BASE_URL + `/store/order`;
- // Request No. 1
- // TODO: edit the parameters of the request body.
- let body = {"id": "long", "petId": "long", "quantity": "integer", "shipDate": "date", "status": "string", "complete": "boolean"};
- let params = {headers: {"Content-Type": "application/json", "Accept": "application/json"}};
- let request = http.post(url, body, params);
- check(request, {
- "successful operation": (r) => r.status === 200
- });
- sleep(SLEEP_DURATION);
- });
- group("/store/order/{orderId}", () => {
- let orderId = 'TODO_EDIT_THE_ORDERID'; // specify value as there is no example value for this parameter in OpenAPI spec
- let url = BASE_URL + `/store/order/${orderId}`;
+ group("/user/login", () => {
+ let password = 'TODO_EDIT_THE_PASSWORD'; // specify value as there is no example value for this parameter in OpenAPI spec
+ let username = 'TODO_EDIT_THE_USERNAME'; // specify value as there is no example value for this parameter in OpenAPI spec
+
// Request No. 1
- let request = http.get(url);
- check(request, {
+ {
+ let url = BASE_URL + `/user/login?username=${username}&password=${password}`;
+
+
+
+ let request = http.get(url);
+
+ check(request, {
"successful operation": (r) => r.status === 200
- });
- sleep(SLEEP_DURATION);
-
- // Request No. 2
- request = http.del(url);
- sleep(SLEEP_DURATION);
+ });
+
+
+ }
+
});
group("/user", () => {
- let url = BASE_URL + `/user`;
+
// Request No. 1
- // TODO: edit the parameters of the request body.
- let body = {"id": "long", "username": "string", "firstName": "string", "lastName": "string", "email": "string", "password": "string", "phone": "string", "userStatus": "integer"};
- let params = {headers: {"Content-Type": "application/json", "Accept": "application/json"}};
- let request = http.post(url, body, params);
- check(request, {
+ {
+ let url = BASE_URL + `/user`;
+ // TODO: edit the parameters of the request body.
+ let body = {"id": "long", "username": "string", "firstName": "string", "lastName": "string", "email": "string", "password": "string", "phone": "string", "userStatus": "integer"};
+
+ let params = {headers: {"Content-Type": "application/json", "Accept": "application/json"}};
+
+
+ let request = http.post(url, JSON.stringify(body), params);
+
+ check(request, {
"successful operation": (r) => r.status === 200
- });
- sleep(SLEEP_DURATION);
+ });
+
+
+ }
+
});
group("/user/createWithArray", () => {
- let url = BASE_URL + `/user/createWithArray`;
+
// Request No. 1
- let params = {headers: {"Content-Type": "application/json", "Accept": "application/json"}};
- let request = http.post(url, params);
- check(request, {
+ {
+ let url = BASE_URL + `/user/createWithArray`;
+
+ let params = {headers: {"Content-Type": "application/json", "Accept": "application/json"}};
+
+
+ let request = http.post(url, params);
+
+ check(request, {
"successful operation": (r) => r.status === 200
- });
- sleep(SLEEP_DURATION);
+ });
+
+
+ }
+
});
- group("/user/createWithList", () => {
- let url = BASE_URL + `/user/createWithList`;
+ group("/pet/findByTags", () => {
+ let tags = 'TODO_EDIT_THE_TAGS'; // specify value as there is no example value for this parameter in OpenAPI spec
+
// Request No. 1
- let params = {headers: {"Content-Type": "application/json", "Accept": "application/json"}};
- let request = http.post(url, params);
- check(request, {
+ {
+ let url = BASE_URL + `/pet/findByTags?tags=${tags}`;
+
+
+
+ let request = http.get(url);
+
+ check(request, {
"successful operation": (r) => r.status === 200
- });
- sleep(SLEEP_DURATION);
+ });
+
+
+ }
+
});
- group("/user/login", () => {
- let password = 'TODO_EDIT_THE_PASSWORD'; // specify value as there is no example value for this parameter in OpenAPI spec
- let username = 'TODO_EDIT_THE_USERNAME'; // specify value as there is no example value for this parameter in OpenAPI spec
- let url = BASE_URL + `/user/login?username=${username}&password=${password}`;
+ group("/store/order", () => {
+
// Request No. 1
- let request = http.get(url);
- check(request, {
+ {
+ let url = BASE_URL + `/store/order`;
+ // TODO: edit the parameters of the request body.
+ let body = {"id": "long", "petId": "long", "quantity": "integer", "shipDate": "date", "status": "string", "complete": "boolean"};
+
+ let params = {headers: {"Content-Type": "application/json", "Accept": "application/json"}};
+
+
+ let request = http.post(url, JSON.stringify(body), params);
+
+ check(request, {
"successful operation": (r) => r.status === 200
- });
- sleep(SLEEP_DURATION);
+ });
+
+
+ }
+
});
group("/user/logout", () => {
- let url = BASE_URL + `/user/logout`;
+
// Request No. 1
- let request = http.get(url);
- check(request, {
+ {
+ let url = BASE_URL + `/user/logout`;
+
+
+
+ let request = http.get(url);
+
+ check(request, {
"successful operation": (r) => r.status === 200
- });
- sleep(SLEEP_DURATION);
+ });
+
+
+ }
+
});
- group("/user/{username}", () => {
- let username = 'TODO_EDIT_THE_USERNAME'; // specify value as there is no example value for this parameter in OpenAPI spec
- let url = BASE_URL + `/user/${username}`;
+ group("/pet/{petId}", () => {
+ let petId = 'TODO_EDIT_THE_PETID'; // specify value as there is no example value for this parameter in OpenAPI spec
+
// Request No. 1
- let request = http.get(url);
- check(request, {
+ {
+ let url = BASE_URL + `/pet/${petId}`;
+
+
+
+ let request = http.get(url);
+
+ check(request, {
"successful operation": (r) => r.status === 200
- });
- sleep(SLEEP_DURATION);
-
+ });
+
+
+ sleep(SLEEP_DURATION);
+ }
+
+
// Request No. 2
- // TODO: edit the parameters of the request body.
- body = {"id": "long", "username": "string", "firstName": "string", "lastName": "string", "email": "string", "password": "string", "phone": "string", "userStatus": "integer"};
- params = {headers: {"Content-Type": "application/json"}};
- request = http.put(url, body, params);
- sleep(SLEEP_DURATION);
-
- // Request No. 3
- request = http.del(url);
- sleep(SLEEP_DURATION);
+ {
+ let url = BASE_URL + `/pet/${petId}`;
+
+ let params = {headers: {"api_key": `${apiKey}`, "Accept": "application/json"}};
+
+ // this is a DELETE method request - if params are also set, empty body must be passed
+ let request = http.del(url, {} , params);
+
+
+
+
+ }
+
+ });
+ group("/store/order/{orderId}", () => {
+ let orderId = 'TODO_EDIT_THE_ORDERID'; // specify value as there is no example value for this parameter in OpenAPI spec
+
+ // Request No. 1
+ {
+ let url = BASE_URL + `/store/order/${orderId}`;
+
+
+
+ let request = http.get(url);
+
+ check(request, {
+ "successful operation": (r) => r.status === 200
+ });
+
+
+ sleep(SLEEP_DURATION);
+ }
+
+
+ // Request No. 2
+ {
+ let url = BASE_URL + `/store/order/${orderId}`;
+
+
+ let request = http.del(url);
+
+
+
+
+ }
+
});
}