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 86bfcfa6520..866e9c2f657 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 @@ -16,28 +16,60 @@ package org.openapitools.codegen.languages; -import io.swagger.v3.oas.models.OpenAPI; -import io.swagger.v3.oas.models.Operation; -import io.swagger.v3.oas.models.PathItem; -import io.swagger.v3.oas.models.info.Info; -import io.swagger.v3.oas.models.info.License; -import io.swagger.v3.oas.models.media.Schema; -import io.swagger.v3.oas.models.parameters.RequestBody; -import io.swagger.v3.oas.models.responses.ApiResponse; -import io.swagger.v3.oas.models.servers.Server; +import static org.openapitools.codegen.utils.StringUtils.camelize; +import static org.openapitools.codegen.utils.StringUtils.dashize; +import static org.openapitools.codegen.utils.StringUtils.underscore; + +import java.io.File; +import java.io.IOException; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.TreeSet; +import java.util.stream.Collectors; + +import javax.annotation.Nullable; + +import org.apache.commons.lang3.StringEscapeUtils; import org.apache.commons.lang3.StringUtils; -import org.openapitools.codegen.*; +import org.openapitools.codegen.CodegenConfig; +import org.openapitools.codegen.CodegenConstants; +import org.openapitools.codegen.CodegenModel; +import org.openapitools.codegen.CodegenParameter; +import org.openapitools.codegen.CodegenProperty; +import org.openapitools.codegen.CodegenResponse; +import org.openapitools.codegen.CodegenType; +import org.openapitools.codegen.DefaultCodegen; +import org.openapitools.codegen.SupportingFile; import org.openapitools.codegen.meta.GeneratorMetadata; import org.openapitools.codegen.meta.Stability; import org.openapitools.codegen.utils.ModelUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.annotation.Nullable; -import java.io.File; -import java.util.*; +import com.google.common.collect.ImmutableMap.Builder; +import com.samskivert.mustache.Mustache; +import com.samskivert.mustache.Mustache.Lambda; +import com.samskivert.mustache.Template; -import static org.openapitools.codegen.utils.StringUtils.*; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.PathItem; +import io.swagger.v3.oas.models.examples.Example; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.info.License; +import io.swagger.v3.oas.models.media.Schema; +import io.swagger.v3.oas.models.parameters.RequestBody; +import io.swagger.v3.oas.models.responses.ApiResponse; +import io.swagger.v3.oas.models.servers.Server; public class K6ClientCodegen extends DefaultCodegen implements CodegenConfig { @@ -50,30 +82,98 @@ public class K6ClientCodegen extends DefaultCodegen implements CodegenConfig { } - static class Parameter { - String key; - Object value; + static class Parameter { + String key; + Object value; + boolean hasExample; - public Parameter(String key, Object value) { - this.key = key; - this.value = value; - } + public Parameter(String key, Object value) { + this.key = key; + this.value = value; + } - @Override - public int hashCode() { - return key.hashCode(); - } + public Parameter(String key, Object exampleValue, boolean hasExample) { + this.key = key; + this.value = exampleValue; + this.hasExample = hasExample; + } - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null || getClass() != obj.getClass()) - return false; - Parameter p = (Parameter) obj; - return key.equals(p.key) && value.equals(p.value); - } - } + @Override + public int hashCode() { + return key.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null || getClass() != obj.getClass()) + return false; + Parameter p = (Parameter) obj; + return key.equals(p.key) && value.equals(p.value) && hasExample == p.hasExample; + } + } + + static class ParameterValueLambda implements Mustache.Lambda { + private static final String NO_EXAMPLE_PARAM_VALUE_PREFIX = "TODO_EDIT_THE_"; + + @Override + public void execute(Template.Fragment fragment, Writer writer) throws IOException { + + // default used if no example is provided + String noExampleParamValue = String.join("", + quoteExample( + String.join("", NO_EXAMPLE_PARAM_VALUE_PREFIX, fragment.execute())), + ";", + " // specify value as there is no example value for this parameter in OpenAPI spec"); + + // param has example(s) + if (fragment.context() instanceof K6ClientCodegen.Parameter + && ((K6ClientCodegen.Parameter) fragment.context()).hasExample) { + + Object rawValue = ((K6ClientCodegen.Parameter) fragment.context()).value; + + // handle as 'examples' + if (rawValue instanceof Map) { + + @SuppressWarnings("unchecked") + Set exampleValues = ((Map) rawValue).values().stream() + .map(x -> quoteExample( + StringEscapeUtils.escapeEcmaScript( + String.valueOf(x.getValue())))) + .collect(Collectors.toCollection(() -> new TreeSet())); + + if (!exampleValues.isEmpty()) { + + writer.write(String.join("", + Arrays.toString(exampleValues.toArray()), + ".shift();", + " // first element from list extracted from 'examples' field defined at the parameter level of OpenAPI spec")); + + } else { + writer.write(noExampleParamValue); + } + + // handle as (single) 'example' + } else { + writer.write(String.join("", + quoteExample( + StringEscapeUtils.escapeEcmaScript( + String.valueOf( + ((K6ClientCodegen.Parameter) fragment.context()).value))), + ";", + " // extracted from 'example' field defined at the parameter level of OpenAPI spec")); + } + + } else { + writer.write(noExampleParamValue); + } + } + + private static String quoteExample(String exampleValue) { + return StringUtils.wrap(exampleValue, "'"); + } + } static class HTTPBody { List parameters; @@ -165,7 +265,7 @@ public class K6ClientCodegen extends DefaultCodegen implements CodegenConfig { } } - private final Logger LOGGER = LoggerFactory.getLogger(JavascriptClientCodegen.class); + private final Logger LOGGER = LoggerFactory.getLogger(K6ClientCodegen.class); public static final String PROJECT_NAME = "projectName"; public static final String MODULE_NAME = "moduleName"; @@ -345,8 +445,8 @@ public class K6ClientCodegen extends DefaultCodegen implements CodegenConfig { } } - List formParameteres = fromRequestBodyToFormParameters(requestBody, imports); - for (CodegenParameter parameter : formParameteres) { + List formParameters = fromRequestBodyToFormParameters(requestBody, imports); + for (CodegenParameter parameter : formParameters) { String reference = ""; if (parameter.isModel) { Schema nestedSchema = ModelUtils.getSchema(openAPI, parameter.baseType); @@ -384,7 +484,23 @@ public class K6ClientCodegen extends DefaultCodegen implements CodegenConfig { case "query": if (parameter.getIn().equals("query")) queryParams.add(new Parameter(parameter.getName(), getTemplateVariable(parameter.getName()))); - variables.add(new Parameter(toVarName(parameter.getName()), parameter.getName().toUpperCase(Locale.ROOT))); + if (!pathVariables.containsKey(path)) { + // use 'example' field defined at the parameter level of OpenAPI spec + if (Objects.nonNull(parameter.getExample())) { + variables.add(new Parameter(toVarName(parameter.getName()), + parameter.getExample(), true)); + + // use 'examples' field defined at the parameter level of OpenAPI spec + } else if (Objects.nonNull(parameter.getExamples())) { + variables.add(new Parameter(toVarName(parameter.getName()), + parameter.getExamples(), true)); + + // no example provided, generated script will contain placeholder value + } else { + variables.add(new Parameter(toVarName(parameter.getName()), + parameter.getName().toUpperCase(Locale.ROOT))); + } + } break; default: break; @@ -432,7 +548,6 @@ public class K6ClientCodegen extends DefaultCodegen implements CodegenConfig { } } - // private String generateNestedModelTemplate(CodegenModel model) { StringBuilder reference = new StringBuilder(); int modelEntrySetSize = model.getAllVars().size(); @@ -629,4 +744,9 @@ public class K6ClientCodegen extends DefaultCodegen implements CodegenConfig { return accepts; } + + @Override + protected Builder addMustacheLambdas() { + return super.addMustacheLambdas().put("handleParamValue", new ParameterValueLambda()); + } } diff --git a/modules/openapi-generator/src/main/resources/k6/README.mustache b/modules/openapi-generator/src/main/resources/k6/README.mustache index c76d4a6de34..64a752161f8 100644 --- a/modules/openapi-generator/src/main/resources/k6/README.mustache +++ b/modules/openapi-generator/src/main/resources/k6/README.mustache @@ -4,6 +4,8 @@ The `script.js` file contains most of the Swagger/OpenAPI specification and you Global header variables are defined at the top of the file, like `api_key`. Each path in the specification is converted into a [group](https://docs.k6.io/docs/tags-and-groups) in k6 and each group contains all the request methods related to that path. Path and query parameters are extracted from the specification and put at the start of the group. The URL is constructed from the base URL plus path and query. +If the Swagger/OpenAPI specification used as the input spec contains examples at parameter level, those will be extracted and utilized as parameter values. The `handleParamValue` custom Mustache lambda registered for use in the K6 `script.mustache` template handles the conditional checks, formatting, and outputting of parameter values. If a given parameter has value specified – either in `example` or `examples` field, defined at the parameter level – that value will be used. For list (`examples`), entire list will be output in the generated script and the first element from that list will be assigned as parameter value. If a given parameter does not have an example defined, a placeholder value with `TODO_EDIT_THE_` prefix will be generated for that parameter, and you will have to assign a value before you can run the script. In other words, you can now generate K6 test scripts which are ready to run, provided the Swagger/OpenAPI specification used as the input spec contains examples for all of the path/query parameters; see `modules/openapi-generator/src/test/resources/3_0/examples.yaml` for an example of such specification, and https://swagger.io/docs/specification/adding-examples/ for more information about adding examples. + k6 specific parameters are in the [`params`](https://docs.k6.io/docs/params-k6http) object, and `body` contains the [request](https://docs.k6.io/docs/http-requests) body which is in the form of `identifier: type`, which the `type` should be substituted by a proper value. Then goes the request and the check. [Check](https://docs.k6.io/docs/checks) are like asserts but differ in that they don't halt execution, instead they just store the result of the check, pass or fail, and let the script execution continue. diff --git a/modules/openapi-generator/src/main/resources/k6/script.mustache b/modules/openapi-generator/src/main/resources/k6/script.mustache index 0fddbc0a31d..cac3381de01 100644 --- a/modules/openapi-generator/src/main/resources/k6/script.mustache +++ b/modules/openapi-generator/src/main/resources/k6/script.mustache @@ -16,7 +16,7 @@ export default function() { {{#requestGroups}} group("{{{groupName}}}", () => { {{#variables}} - let {{{key}}} = "TODO_EDIT_THE_{{{value}}}"; + let {{{key}}} = {{#lambda.handleParamValue}}{{value}}{{/lambda.handleParamValue}} {{/variables}} {{#requests}} {{#-first}} diff --git a/modules/openapi-generator/src/test/resources/3_0/examples.yaml b/modules/openapi-generator/src/test/resources/3_0/examples.yaml index 07893343180..3a1037830cb 100644 --- a/modules/openapi-generator/src/test/resources/3_0/examples.yaml +++ b/modules/openapi-generator/src/test/resources/3_0/examples.yaml @@ -42,7 +42,7 @@ paths: in: query schema: type: string - example: 'example2 value' + example: 'example2 value' responses: '200': description: successful operation @@ -61,7 +61,7 @@ paths: description: successful operation /example3/plural: get: - operationId: example3GetSingular + operationId: example3GetPlural parameters: - name: parameter in: query @@ -92,7 +92,7 @@ paths: description: successful operation /example4/plural: post: - operationId: example4PostSingular + operationId: example4PostPlural requestBody: content: application/x-www-form-urlencoded: diff --git a/samples/client/petstore/k6/.openapi-generator/VERSION b/samples/client/petstore/k6/.openapi-generator/VERSION index d99e7162d01..6555596f931 100644 --- a/samples/client/petstore/k6/.openapi-generator/VERSION +++ b/samples/client/petstore/k6/.openapi-generator/VERSION @@ -1 +1 @@ -5.0.0-SNAPSHOT \ No newline at end of file +5.2.0-SNAPSHOT \ No newline at end of file diff --git a/samples/client/petstore/k6/README.md b/samples/client/petstore/k6/README.md index c76d4a6de34..64a752161f8 100644 --- a/samples/client/petstore/k6/README.md +++ b/samples/client/petstore/k6/README.md @@ -4,6 +4,8 @@ The `script.js` file contains most of the Swagger/OpenAPI specification and you Global header variables are defined at the top of the file, like `api_key`. Each path in the specification is converted into a [group](https://docs.k6.io/docs/tags-and-groups) in k6 and each group contains all the request methods related to that path. Path and query parameters are extracted from the specification and put at the start of the group. The URL is constructed from the base URL plus path and query. +If the Swagger/OpenAPI specification used as the input spec contains examples at parameter level, those will be extracted and utilized as parameter values. The `handleParamValue` custom Mustache lambda registered for use in the K6 `script.mustache` template handles the conditional checks, formatting, and outputting of parameter values. If a given parameter has value specified – either in `example` or `examples` field, defined at the parameter level – that value will be used. For list (`examples`), entire list will be output in the generated script and the first element from that list will be assigned as parameter value. If a given parameter does not have an example defined, a placeholder value with `TODO_EDIT_THE_` prefix will be generated for that parameter, and you will have to assign a value before you can run the script. In other words, you can now generate K6 test scripts which are ready to run, provided the Swagger/OpenAPI specification used as the input spec contains examples for all of the path/query parameters; see `modules/openapi-generator/src/test/resources/3_0/examples.yaml` for an example of such specification, and https://swagger.io/docs/specification/adding-examples/ for more information about adding examples. + k6 specific parameters are in the [`params`](https://docs.k6.io/docs/params-k6http) object, and `body` contains the [request](https://docs.k6.io/docs/http-requests) body which is in the form of `identifier: type`, which the `type` should be substituted by a proper value. Then goes the request and the check. [Check](https://docs.k6.io/docs/checks) are like asserts but differ in that they don't halt execution, instead they just store the result of the check, pass or fail, and let the script execution continue. diff --git a/samples/client/petstore/k6/script.js b/samples/client/petstore/k6/script.js index 3106a7a7c0f..a9f5e500e7e 100644 --- a/samples/client/petstore/k6/script.js +++ b/samples/client/petstore/k6/script.js @@ -1,13 +1,13 @@ /* * OpenAPI Petstore - * This is a sample server Petstore server. For this sample, you can use the api key \"special-key\" to test the authorization filters + * This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. * * OpenAPI spec version: 1.0.0 * * NOTE: This class is auto generated by OpenAPI Generator. * https://github.com/OpenAPITools/openapi-generator * - * OpenAPI generator version: 5.0.0-SNAPSHOT + * OpenAPI generator version: 5.2.0-SNAPSHOT */ @@ -39,7 +39,7 @@ export default function() { sleep(SLEEP_DURATION); }); group("/pet/findByStatus", () => { - let status = "TODO_EDIT_THE_STATUS"; + 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}`; // Request No. 1 let request = http.get(url); @@ -49,7 +49,7 @@ export default function() { sleep(SLEEP_DURATION); }); group("/pet/findByTags", () => { - let tags = "TODO_EDIT_THE_TAGS"; + 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}`; // Request No. 1 let request = http.get(url); @@ -59,7 +59,7 @@ export default function() { sleep(SLEEP_DURATION); }); group("/pet/{petId}", () => { - let petId = "TODO_EDIT_THE_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}`; // Request No. 1 let request = http.get(url); @@ -81,7 +81,7 @@ export default function() { sleep(SLEEP_DURATION); }); group("/pet/{petId}/uploadImage", () => { - let petId = "TODO_EDIT_THE_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}/uploadImage`; // Request No. 1 // TODO: edit the parameters of the request body. @@ -115,7 +115,7 @@ export default function() { sleep(SLEEP_DURATION); }); group("/store/order/{orderId}", () => { - let orderId = "TODO_EDIT_THE_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}`; // Request No. 1 let request = http.get(url); @@ -161,8 +161,8 @@ export default function() { sleep(SLEEP_DURATION); }); group("/user/login", () => { - let password = "TODO_EDIT_THE_PASSWORD"; - let username = "TODO_EDIT_THE_USERNAME"; + 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}`; // Request No. 1 let request = http.get(url); @@ -181,7 +181,7 @@ export default function() { sleep(SLEEP_DURATION); }); group("/user/{username}", () => { - let username = "TODO_EDIT_THE_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}`; // Request No. 1 let request = http.get(url);