From 1e445fbf4897ee991b1e04609d8aee6bc92008f3 Mon Sep 17 00:00:00 2001 From: Jens Schulze Date: Thu, 23 May 2024 09:48:22 +0200 Subject: [PATCH 01/20] add bruno module with stubs --- languages/bruno/build.gradle | 8 + .../languages/bruno/model/ActionRenderer.kt | 176 +++++++++++ .../bruno/model/AuthorizationRenderer.kt | 273 ++++++++++++++++++ .../languages/bruno/model/BrunoBaseTypes.kt | 20 ++ .../languages/bruno/model/BrunoExtensions.kt | 44 +++ .../languages/bruno/model/BrunoModelModule.kt | 16 + .../bruno/model/BrunoModuleRenderer.kt | 176 +++++++++++ .../codegen/languages/bruno/model/BrunoUrl.kt | 78 +++++ .../languages/bruno/model/MethodRenderer.kt | 78 +++++ .../languages/bruno/model/ResourceRenderer.kt | 38 +++ .../languages/bruno/model/Serializers.kt | 29 ++ .../languages/bruno/TestCodeGenerator.kt | 42 +++ settings.gradle | 1 + tools/cli-application/build.gradle | 1 + .../rmf/codegen/cli/GenerateSubcommand.kt | 11 +- 15 files changed, 989 insertions(+), 2 deletions(-) create mode 100644 languages/bruno/build.gradle create mode 100644 languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/ActionRenderer.kt create mode 100644 languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/AuthorizationRenderer.kt create mode 100644 languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoBaseTypes.kt create mode 100644 languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoExtensions.kt create mode 100644 languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModelModule.kt create mode 100644 languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModuleRenderer.kt create mode 100644 languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoUrl.kt create mode 100644 languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/MethodRenderer.kt create mode 100644 languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/ResourceRenderer.kt create mode 100644 languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/Serializers.kt create mode 100644 languages/bruno/src/test/kotlin/io/vrap/codegen/languages/bruno/TestCodeGenerator.kt diff --git a/languages/bruno/build.gradle b/languages/bruno/build.gradle new file mode 100644 index 000000000..6e262600d --- /dev/null +++ b/languages/bruno/build.gradle @@ -0,0 +1,8 @@ + +dependencies { + implementation project(':codegen-renderers') + implementation commons.lang3 + implementation commons.text + implementation orgkotlin.reflect + implementation orgkotlin.stdlib +} diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/ActionRenderer.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/ActionRenderer.kt new file mode 100644 index 000000000..ab8aa6411 --- /dev/null +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/ActionRenderer.kt @@ -0,0 +1,176 @@ +package io.vrap.codegen.languages.bruno.model + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.node.ObjectNode +import io.vrap.codegen.languages.extensions.resource +import io.vrap.rmf.codegen.firstUpperCase +import io.vrap.rmf.codegen.rendering.utils.escapeAll +import io.vrap.rmf.codegen.rendering.utils.keepAngleIndent +import io.vrap.rmf.raml.model.resources.HttpMethod +import io.vrap.rmf.raml.model.resources.Method +import io.vrap.rmf.raml.model.resources.Resource +import io.vrap.rmf.raml.model.types.* +import io.vrap.rmf.raml.model.util.StringCaseFormat +import java.io.IOException +import java.util.* + +class ActionRenderer { + fun render(resource: Resource): List { + if (resource.getAnnotation("updateable") == null) return emptyList() + + val updateMethod = resource.getUpdateMethod() + + val actions = updateMethod?.getActions()?.filterNot { objType -> objType.deprecated() }?.map { renderAction(resource, updateMethod, it) } ?: return emptyList() + + return listOf(""" + |{ + | "name": "Update actions", + | "item": [ + | <<${actions.joinToString(",\n") }>> + | ] + |} + """.trimMargin()) + } + + private fun renderAction(resource: Resource, method: Method, type: ObjectType): String { + val url = BrunoUrl(method.resource(), method) { resource, name -> when (name) { + "ID" -> resource.resourcePathName.singularize() + "-id" + "key" -> resource.resourcePathName.singularize() + "-key" + else -> StringCaseFormat.LOWER_HYPHEN_CASE.apply(name) + }} + return """ + |{ + | "name": "${type.discriminatorValue.firstUpperCase()}${if (type.markDeprecated()) " (deprecated)" else ""}", + | "event": [ + | { + | "listen": "test", + | "script": { + | "type": "text/javascript", + | "exec": [ + | <<${resource.testScript()}>> + | ] + | } + | } + | ], + | "request": { + | "auth": + | <<${auth()}>>, + | "method": "${method.methodName}", + | "body": { + | "mode": "raw", + | "raw": "${resource.actionExample(type).escapeJson().escapeAll()}" + | }, + | "header": [ + | { + | "key": "Content-Type", + | "value": "application/json" + | } + | ], + | "url": { + | "raw": "${url.raw()}", + | "host": [ + | "{{host}}" + | ], + | "path": [ + | <<"${url.path()}">> + | ], + | "query": [ + | <<${url.query()}>> + | ] + | }, + | "description": "${method.description()}" + | }, + | "response": [] + |} + """.trimMargin() + } + + private fun ObjectType.deprecated() : Boolean { + val anno = this.getAnnotation("deprecated") + return (anno != null && (anno.value as BooleanInstance).value) + } + + private fun ObjectType.markDeprecated() : Boolean { + val anno = this.getAnnotation("markDeprecated") + return (anno != null && (anno.value as BooleanInstance).value) + } + + + private fun Resource.actionExample(type: ObjectType): String { + val example = getExample(type) + return """ + |{ + | "version": {{${this.resourcePathName.singularize()}-version}}, + | "actions": [ + | <<${if (example.isNullOrEmpty().not()) example else """ + | |{ + | | "action": "${type.discriminatorValue}" + | |}""".trimMargin()}>> + | ] + |} + """.trimMargin().keepAngleIndent() + } + + private fun getExample(type: ObjectType): String? { + var example: String? = null + var instance: Instance? = null + + if (type.getAnnotation("postman-example") != null) { + instance = type.getAnnotation("postman-example").value + } else if (type.examples.size > 0) { + instance = type.examples[0].value + } + + if (instance != null) { + example = instance.toJson() + try { + val mapper = ObjectMapper() + val nodes = mapper.readTree(example) as ObjectNode + nodes.put("action", type.discriminatorValue) + + example = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(nodes) + .split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray().map { s -> " $s" } + .joinToString("\n") + .trim { it <= ' ' } + } catch (e: IOException) { + } + + } + + return example + } + +// private fun getTestScript(type: ObjectType): String? { +// val t = type.getAnnotation("postman-test-script") +// if (t != null) return (t.value as StringInstance).value +// +// return null +// } + + private fun Resource.getUpdateMethod(): Method? { + val byIdResource = this.resources.find { resource -> resource.relativeUri.template == "/{ID}" } ?: return null + + return byIdResource.getMethod(HttpMethod.POST) + } + + private fun Method.getActions(): List { + val body = this.getBody("application/json") ?: return emptyList() + + val actions = (body.type as ObjectType).getProperty("actions") ?: return emptyList() + + val actionsType = actions.type as ArrayType + val updateActions = if (actionsType.items is UnionType) { + (actionsType.items as UnionType).oneOf[0].subTypes + } else { + actionsType.items.subTypes + } + val actionItems = updateActions.map { action -> action as ObjectType }.sortedBy { action -> action.discriminatorValue } + return actionItems + } + + fun Method.description(): String { + val name = StringCaseFormat.UPPER_CAMEL_CASE.apply(Optional.ofNullable(this.resource().displayName).map { it.value }.orElse(this.resource().resourcePathName)) + val description = Optional.ofNullable(this.description).map { it.value }.orElse(this.method.getName() + " " + name) + return description.escapeJson().escapeAll() + } +} diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/AuthorizationRenderer.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/AuthorizationRenderer.kt new file mode 100644 index 000000000..200764841 --- /dev/null +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/AuthorizationRenderer.kt @@ -0,0 +1,273 @@ +package io.vrap.codegen.languages.bruno.model + +import io.vrap.rmf.codegen.rendering.utils.escapeAll +import io.vrap.rmf.codegen.rendering.utils.keepAngleIndent +import io.vrap.rmf.raml.model.security.OAuth20Settings + +fun authorization(oauth: OAuth20Settings): String { + return """ + |{ + | "name": "Authorization", + | "description": "Authorization", + | "item": [ + | { + | "name": "Obtain access token", + | "event": [ + | { + | "listen": "test", + | "script": { + | "type": "text/javascript", + | "exec": [ + | <<${testAuthScript().jScript()}>> + | ] + | } + | } + | ], + | "request": { + | "auth": { + | "type": "basic", + | "basic": { + | "username": "{{client_id}}", + | "password": "{{client_secret}}" + | } + | }, + | "method": "POST", + | "header": [], + | "body": { + | "mode": "raw", + | "raw": "" + | }, + | "url": { + | "raw": "{{auth_url}}${oauth.uri().path}?grant_type=client_credentials", + | "host": [ + | "{{auth_url}}" + | ], + | "path": [ + | "oauth", + | "token" + | ], + | "query": [ + | { + | "key": "grant_type", + | "value": "client_credentials", + | "equals": true + | } + | ] + | }, + | "description": "Use this request to obtain an access token for your commercetools platform project via Client Credentials Flow. As a prerequisite you must have filled out environment variables in Postman for projectKey, client_id and client_secret to use this." + | }, + | "response": [] + | }, + | { + | "name": "Obtain access token through password flow", + | "event": [ + | { + | "listen": "test", + | "script": { + | "type": "text/javascript", + | "exec": [ + | <<${testAuthScript().jScript()}>> + | ] + | } + | } + | ], + | "request": { + | "auth": { + | "type": "basic", + | "basic": { + | "username": "{{client_id}}", + | "password": "{{client_secret}}" + | } + | }, + | "method": "POST", + | "header": [ + | { + | "key": "", + | "value": "", + | "disabled": true + | } + | ], + | "body": { + | "mode": "raw", + | "raw": "" + | }, + | "url": { + | "raw": "{{auth_url}}/oauth/{{project-key}}/customers/token?grant_type=password&username={{user_email}}&password={{user_password}}", + | "host": [ + | "{{auth_url}}" + | ], + | "path": [ + | "oauth", + | "{{project-key}}", + | "customers", + | "token" + | ], + | "query": [ + | { + | "key": "grant_type", + | "value": "password", + | "equals": true + | }, + | { + | "key": "username", + | "value": "", + | "equals": true + | }, + | { + | "key": "password", + | "value": "", + | "equals": true + | } + | ] + | }, + | "description": "Use this request to obtain an access token for your commercetools platform project via Password Flow. As a prerequisite you must have filled out environment variables in Postman for projectKey, client_id, client_secret, user_email and user_password to use this." + | }, + | "response": [] + | }, + | { + | "name": "Token for Anonymous Sessions", + | "event": [ + | { + | "listen": "test", + | "script": { + | "type": "text/javascript", + | "exec": [ + | <<${testAuthScript().jScript()}>> + | ] + | } + | } + | ], + | "request": { + | "auth": { + | "type": "basic", + | "basic": { + | "username": "{{client_id}}", + | "password": "{{client_secret}}" + | } + | }, + | "method": "POST", + | "header": [], + | "body": { + | "mode": "raw", + | "raw": "" + | }, + | "url": { + | "raw": "{{auth_url}}/oauth/{{project-key}}/anonymous/token?grant_type=client_credentials", + | "host": [ + | "{{auth_url}}" + | ], + | "path": [ + | "oauth", + | "{{project-key}}", + | "anonymous", + | "token" + | ], + | "query": [ + | { + | "key": "grant_type", + | "value": "client_credentials", + | "equals": true + | } + | ] + | }, + | "description": "Use this request to obtain an access token for a anonymous session. As a prerequisite you must have filled out environment variables in Postman for projectKey, client_id and client_secret to use this." + | }, + | "response": [] + | }, + | { + | "name": "Token Introspection", + | "event": [ + | { + | "listen": "test", + | "script": { + | "type": "text/javascript", + | "exec": [ + | <<${ "tests[\"Status code is 200\"] = responseCode.code === 200;".jScript() }>> + | ] + | } + | } + | ], + | "request": { + | "auth": { + | "type": "basic", + | "basic": { + | "username": "{{client_id}}", + | "password": "{{client_secret}}" + | } + | }, + | "method": "POST", + | "header": [ + | { + | "key": "Content-Type", + | "value": "application/json" + | } + | ], + | "body": { + | "mode": "raw", + | "raw": "" + | }, + | "url": { + | "raw": "{{auth_url}}/oauth/introspect?token={{ctp_access_token}}", + | "host": [ + | "{{auth_url}}" + | ], + | "path": [ + | "oauth", + | "introspect" + | ], + | "query": [ + | { + | "key": "token", + | "value": "{{ctp_access_token}}", + | "equals": true + | } + | ] + | }, + | "description": "Token introspection allows to determine the active state of an OAuth 2.0 access token and to determine meta-information about this accces token, such as the `scope`." + | }, + | "response": [] + | } + | ] + |} + """.trimMargin().keepAngleIndent().escapeAll() +} + +private fun testAuthScript(): String { + return """ + |tests["Status code is 200"] = responseCode.code === 200; + |var data = JSON.parse(responseBody); + |if(data.access_token){ + | pm.environment.set("ctp_access_token", data.access_token); + |} + |if (data.scope) { + | parts = data.scope.split(" "); + | parts = parts.filter(scope => scope.includes(":")).map(scope => scope.split(":")) + | if (parts.length > 0) { + | scopeParts = parts[0]; + | pm.environment.set("project-key", scopeParts[1]); + | parts = parts.filter(scope => scope.length >= 3) + | if (parts.length > 0) { + | scopeParts = parts[0]; + | pm.environment.set("store-key", scopeParts[2]); + | } + | } + |} + """.trimMargin() +} + +fun auth(): String { + return """ + |{ + | "type": "oauth2", + | "oauth2": { + | "accessToken": "{{ctp_access_token}}", + | "addTokenTo": "header", + | "tokenType": "Bearer" + | } + |} + """.trimMargin() +} + +private fun String.jScript(): String { + return this.split("\n").map { it.escapeJson().escapeAll() }.joinToString("\",\n\"", "\"", "\""); +} diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoBaseTypes.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoBaseTypes.kt new file mode 100644 index 000000000..51db49d98 --- /dev/null +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoBaseTypes.kt @@ -0,0 +1,20 @@ +package io.vrap.codegen.languages.bruno.model + +import io.vrap.rmf.codegen.types.LanguageBaseTypes +import io.vrap.rmf.codegen.types.VrapScalarType + +object BrunoBaseTypes : LanguageBaseTypes( + anyType = nativePostmanType("any"), + objectType = nativePostmanType("object"), + integerType = nativePostmanType("number"), + longType = nativePostmanType("number"), + doubleType = nativePostmanType("number"), + stringType = nativePostmanType("string"), + booleanType = nativePostmanType("boolean"), + dateTimeType = nativePostmanType("string"), + dateOnlyType = nativePostmanType("string"), + timeOnlyType = nativePostmanType("string"), + file = nativePostmanType("Buffer") +) + +fun nativePostmanType(typeName: String): VrapScalarType = VrapScalarType(typeName) diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoExtensions.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoExtensions.kt new file mode 100644 index 000000000..a43232390 --- /dev/null +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoExtensions.kt @@ -0,0 +1,44 @@ +package io.vrap.codegen.languages.bruno.model + +import com.hypertino.inflector.English +import io.vrap.rmf.codegen.rendering.utils.escapeAll +import io.vrap.rmf.raml.model.modules.Api +import io.vrap.rmf.raml.model.security.OAuth20Settings +import io.vrap.rmf.raml.model.types.StringInstance +import org.apache.commons.text.StringEscapeUtils +import org.eclipse.emf.ecore.EObject +import java.net.URI + + +fun String.escapeJson(): String { + return StringEscapeUtils.escapeJson(this) +} + +fun OAuth20Settings.uri(): URI { + return URI.create(this.accessTokenUri) +} + +fun Api.oAuth2(): OAuth20Settings { + return this.securitySchemes.stream() + .filter { securityScheme -> securityScheme.settings is OAuth20Settings } + .map { securityScheme -> securityScheme.settings as OAuth20Settings } + .findFirst().orElse(null) +} + +fun String.singularize(): String { + return English.singular(this) +} + +@Suppress("UNCHECKED_CAST") +fun EObject.getParent(parentClass: Class): T? { + if (this.eContainer() == null) { + return null + } + return if (parentClass.isInstance(this.eContainer())) { + this.eContainer() as T + } else this.eContainer().getParent(parentClass) +} + +fun StringInstance.description(): String { + return this.value.escapeJson().escapeAll() +} diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModelModule.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModelModule.kt new file mode 100644 index 000000000..3eac5f794 --- /dev/null +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModelModule.kt @@ -0,0 +1,16 @@ +package io.vrap.codegen.languages.bruno.model + +import io.vrap.rmf.codegen.di.RamlGeneratorModule +import io.vrap.rmf.codegen.di.Module +import io.vrap.rmf.codegen.rendering.CodeGenerator +import io.vrap.rmf.codegen.rendering.FileGenerator + +object BrunoModelModule : Module { + override fun configure(generatorModule: RamlGeneratorModule) = setOf( + FileGenerator( + setOf( + BrunoModuleRenderer(generatorModule.provideRamlModel(), generatorModule.vrapTypeProvider()) + ) + ) + ) +} diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModuleRenderer.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModuleRenderer.kt new file mode 100644 index 000000000..8ecb60989 --- /dev/null +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModuleRenderer.kt @@ -0,0 +1,176 @@ +package io.vrap.codegen.languages.bruno.model + +import com.fasterxml.jackson.core.JsonProcessingException +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.module.SimpleModule +import io.vrap.codegen.languages.extensions.EObjectExtensions +import io.vrap.rmf.codegen.io.TemplateFile +import io.vrap.rmf.codegen.rendering.FileProducer +import io.vrap.rmf.codegen.rendering.utils.escapeAll +import io.vrap.rmf.codegen.rendering.utils.keepAngleIndent +import io.vrap.rmf.codegen.types.VrapTypeProvider +import io.vrap.rmf.raml.model.modules.Api +import io.vrap.rmf.raml.model.resources.Resource +import io.vrap.rmf.raml.model.types.* + +class BrunoModuleRenderer constructor(val api: Api, override val vrapTypeProvider: VrapTypeProvider) : EObjectExtensions, FileProducer { + + override fun produceFiles(): List { + return listOf( + template(api), + collection(api) + ) + } + + private fun template(api: Api): TemplateFile { + return TemplateFile(relativePath = "template.json", + content = """ + |{ + | "id": "5bb74f05-5e78-4aee-b59e-492c947bc160", + | "name": "${api.title}.template", + | "values": [ + | { + | "enabled": true, + | "key": "host", + | "value": "${api.baseUri.template.trimEnd('/')}", + | "type": "text" + | }, + | { + | "enabled": true, + | "key": "auth_url", + | "value": "https://${api.oAuth2().uri().host}", + | "type": "text" + | }, + | { + | "enabled": true, + | "key": "client_id", + | "value": "", + | "type": "text" + | }, + | { + | "enabled": true, + | "key": "client_secret", + | "value": "", + | "type": "text" + | }, + | { + | "enabled": true, + | "key": "ctp_access_token", + | "value": "", + | "type": "text" + | } + | ] + |} + """.trimMargin() + ) + } + + private fun collection(api: Api): TemplateFile { + return TemplateFile(relativePath = "collection.json", + content = """ + |{ + | "info": { + | "_postman_id": "f367b534-c9ea-e7c5-1f46-7a27dc6a30ba", + | "name": "${api.title}", + | "description": "${readme(api).escapeJson().escapeAll()}", + | "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json" + | }, + | "auth": + | <<${auth()}>>, + | "item": [ + | <<${authorization(api.oAuth2())}>>, + | <<${api.resources.joinToString(",") { ResourceRenderer().render(it) }}>> + | ] + |} + """.trimMargin().keepAngleIndent()) + } + + private fun readme(api: Api): String { + return """ + # commercetools API Postman Collection + + This Postman collection contains examples of requests and responses for most endpoints and commands of the + ${api.title}. For every command the smallest possible payload is given. Please find optional + fields in the related official documentation. Additionally the collection provides example requests and + responses for specific tasks and more complex data models. + + ## Disclaimer + + This is not the official ${api.title} documentation. Please see [here](http://docs.commercetools.com/) + for a complete and approved documentation of the ${api.title}. + + ## How to use + + **:warning: Be aware that postman automatically synchronizes environment variables (including your API client credentials) to your workspace if logged in. + Use this collection only for development purposes and non-production projects.** + + To use this collection in Postman please perform the following steps: + + 1. Download and install the Postman Client + 2. Import the [collection.json](collection.json) and [template.json](template.json) in your postman application + 3. In the Merchant Center, create a new API Client and fill in the client credentials in your environment + 4. Obtain an access token by sending the "Authorization/Obtain access token" request at the bottom of the request list. Now you can use all other endpoints + + Feel free to clone and modify this collection to your needs. + + To automate frequent tasks the collection automatically manages commonly required values and parameters such + as resource ids, keys and versions in Postman environment variables for you. + + Please see http://docs.commercetools.com/ for further information about the commercetools Plattform. + """.trimIndent() + } +} + +fun Instance.toJson(): String { + var example = "" + val mapper = ObjectMapper() + + val module = SimpleModule() + module.addSerializer(ObjectInstance::class.java, ObjectInstanceSerializer()) + module.addSerializer(ArrayInstance::class.java, InstanceSerializer()) + module.addSerializer(IntegerInstance::class.java, InstanceSerializer()) + module.addSerializer(BooleanInstance::class.java, InstanceSerializer()) + module.addSerializer(StringInstance::class.java, InstanceSerializer()) + module.addSerializer(NumberInstance::class.java, InstanceSerializer()) + mapper.registerModule(module) + + if (this is StringInstance) { + example = this.value + } else if (this is ObjectInstance) { + try { + example = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(this) + } catch (e: JsonProcessingException) { + } + + } + + return example +} + +fun Resource.testScript(param: String = ""): String { + return """ + |tests["Status code " + responseCode.code] = responseCode.code === 200 || responseCode.code === 201; + |var data = JSON.parse(responseBody); + |if(data.results && data.results[0] && data.results[0].id && data.results[0].version){ + | pm.environment.set("${this.resourcePathName.singularize()}-id", data.results[0].id); + | pm.environment.set("${this.resourcePathName.singularize()}-version", data.results[0].version); + |} + |if(data.results && data.results[0] && data.results[0].key){ + | pm.environment.set("${this.resourcePathName.singularize()}-key", data.results[0].key); + |} + |if(data.version){ + | pm.environment.set("${this.resourcePathName.singularize()}-version", data.version); + |} + |if(data.id){ + | pm.environment.set("${this.resourcePathName.singularize()}-id", data.id); + |} + |if(data.key){ + | pm.environment.set("${this.resourcePathName.singularize()}-key", data.key); + |} + |${if (param.isNotEmpty()) """ + |if(data.${param}){ + | pm.environment.set("${this.resourcePathName.singularize()}-${param}", data.${param}); + |} + """.trimMargin() else ""} + """.trimMargin().split("\n").map { it.escapeJson().escapeAll() }.joinToString("\",\n\"", "\"", "\"") +} diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoUrl.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoUrl.kt new file mode 100644 index 000000000..2df18932d --- /dev/null +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoUrl.kt @@ -0,0 +1,78 @@ +package io.vrap.codegen.languages.bruno.model + +import com.damnhandy.uri.template.Expression +import com.damnhandy.uri.template.UriTemplateComponent +import io.vrap.rmf.raml.model.resources.Method +import io.vrap.rmf.raml.model.resources.Resource +import io.vrap.rmf.raml.model.types.QueryParameter +import io.vrap.rmf.raml.model.types.StringInstance +import org.eclipse.emf.ecore.EObject + +class BrunoUrl (private val resource: Resource, private val method: Method, val renameParam: Function2) { + + fun host(): String { + return "{{host}}" + } + + private fun transformUri(resource: Resource, uriComponent: UriTemplateComponent): String { + if (uriComponent is Expression && uriComponent.varSpecs.size == 1) { + val paramName = uriComponent.varSpecs.first().value + val uriParameter = resource.uriParameters.find { it.name.equals(paramName) && it.type?.getAnnotation("paramName") != null } + val param = if (uriParameter != null) uriParameter.type.getAnnotation("paramName").value.value else renameParam(resource, paramName) + return "{{${param}}}" + } + else return uriComponent.toString() + } + + private fun postmanUrlPath(): String { + return postmanUrlPath(resource).joinToString("") + } + + private fun postmanUrlPath(eObject: EObject): List { + return when (eObject) { + is Resource -> { + val pathes = postmanUrlPath(eObject.eContainer()) + pathes.plus(eObject.relativeUri.components.joinToString("") { uriTemplateComponent -> transformUri(eObject, uriTemplateComponent) }) + } + else -> + emptyList() + } + } + + fun raw(): String { + return "${host()}${postmanUrlPath()}" + } + + fun path(drop: Int = 1): String { + return postmanUrlPath().split("/").drop(drop).joinToString("\",\n\"") + } + + fun query(): String { + return method.queryParameters.joinToString(",\n") { it.queryParam() } + } + + private fun QueryParameter.queryParam() : String { + return """ + |{ + | "key": "${this.name}", + | "value": "${this.defaultValue()}", + | "equals": true, + | "disabled": ${this.required.not()} + |} + """.trimMargin() + } + + fun QueryParameter.defaultValue(): String { + if (this.name == "version") { + return "{{" + this.getParent(Resource::class.java)?.resourcePathName?.singularize() + "-version}}" + } + val defaultValue = this.getAnnotation("postman-default-value") + if (defaultValue != null && defaultValue.value is StringInstance) { + val value = (defaultValue.value.value as String).replace("{{", "").replace("}}", "") + + return "{{" + this.getParent(Resource::class.java)?.resourcePathName?.singularize() + "-" + value + "}}" + } + + return "" + } +} diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/MethodRenderer.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/MethodRenderer.kt new file mode 100644 index 000000000..a1a129d99 --- /dev/null +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/MethodRenderer.kt @@ -0,0 +1,78 @@ +package io.vrap.codegen.languages.bruno.model + +import io.vrap.codegen.languages.extensions.resource +import io.vrap.codegen.languages.extensions.toResourceName +import io.vrap.rmf.codegen.rendering.utils.escapeAll +import io.vrap.rmf.raml.model.resources.Method +import io.vrap.rmf.raml.model.util.StringCaseFormat +import org.apache.commons.text.StringEscapeUtils + +class MethodRenderer { + fun render(method: Method): String { + + val url = BrunoUrl(method.resource(), method) { resource, name -> when (name) { + "ID" -> resource.resourcePathName.singularize() + "-id" + "key" -> resource.resourcePathName.singularize() + "-key" + else -> StringCaseFormat.LOWER_HYPHEN_CASE.apply(name) + }} + return """ + |{ + | "name": "${method.displayName?.value ?: "${method.methodName} ${method.resource().toResourceName()}" }", + | "event": [ + | { + | "listen": "test", + | "script": { + | "type": "text/javascript", + | "exec": [ + | <<${method.resource().testScript()}>> + | ] + | } + | } + | ], + | "request": { + | "auth": + | <<${auth()}>>, + | "method": "${method.methodName}", + | "header": [ + | { + | "key": "Content-Type", + | "value": "application/json" + | } + | ], + | "url": { + | "raw": "${url.raw()}", + | "host": [ + | "{{host}}" + | ], + | "path": [ + | <<"${url.path()}">> + | ], + | "query": [ + | <<${url.query()}>> + | ] + | }, + | "description": "${method.description?.description()}", + | "body": { + | "mode": "raw", + | "raw": "${method.rawBody()}" + | } + | }, + | "response": [] + |} + """.trimMargin() + } + + fun Method.getExample(): String? { + val s = this.bodies?. + getOrNull(0)?. + type?. + examples?. + getOrNull(0)?. + value + return StringEscapeUtils.escapeJson(s?.toJson()) + } + + fun Method.rawBody(): String { + return this.getExample()?.escapeAll()?:"" + } +} diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/ResourceRenderer.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/ResourceRenderer.kt new file mode 100644 index 000000000..1b2e5f058 --- /dev/null +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/ResourceRenderer.kt @@ -0,0 +1,38 @@ +package io.vrap.codegen.languages.bruno.model + +import io.vrap.rmf.codegen.firstUpperCase +import io.vrap.rmf.raml.model.resources.Resource +import io.vrap.rmf.raml.model.types.BooleanInstance + +class ResourceRenderer { + fun render(resource: Resource): String { + + val items = resource.resources.filterNot { res -> res.deprecated() || res.markDeprecated() }.map { ResourceRenderer().render(it) } + .plus(resource.methods.map { MethodRenderer().render(it) }) + .plus(ActionRenderer().render(resource)) + + if ((resource.relativeUri.template.contains(resource.resourcePathName).not() && resource.relativeUriParameters.size > 0) || resource.resources.size == 0) { + return items.joinToString(",\n") + } + return """ + |{ + | "name": "${resource.displayName?.value ?: resource.resourcePathName.firstUpperCase()}", + | "description": "${resource.description?.description()}", + | "item": [ + | <<${items.joinToString(",\n")}>> + | ] + |} + """.trimMargin() + } + + private fun Resource.deprecated() : Boolean { + val anno = this.getAnnotation("deprecated") + return (anno != null && (anno.value as BooleanInstance).value) + } + + private fun Resource.markDeprecated() : Boolean { + val anno = this.getAnnotation("markDeprecated") + return (anno != null && (anno.value as BooleanInstance).value) + } + +} diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/Serializers.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/Serializers.kt new file mode 100644 index 000000000..1c882d851 --- /dev/null +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/Serializers.kt @@ -0,0 +1,29 @@ +package io.vrap.codegen.languages.bruno.model + +import com.fasterxml.jackson.core.JsonGenerator +import com.fasterxml.jackson.databind.SerializerProvider +import com.fasterxml.jackson.databind.ser.std.StdSerializer +import io.vrap.rmf.raml.model.types.Instance +import io.vrap.rmf.raml.model.types.ObjectInstance +import java.io.IOException + +class InstanceSerializer @JvmOverloads constructor(t: Class? = null) : StdSerializer(t) { + + @Throws(IOException::class) + override fun serialize(value: Instance, gen: JsonGenerator, provider: SerializerProvider) { + gen.writeObject(value.value) + } +} + +class ObjectInstanceSerializer @JvmOverloads constructor(t: Class? = null) : StdSerializer(t) { + + @Throws(IOException::class) + override fun serialize(value: ObjectInstance, gen: JsonGenerator, provider: SerializerProvider) { + val properties = value.value + gen.writeStartObject() + for (v in properties) { + gen.writeObjectField(v.name, v.value) + } + gen.writeEndObject() + } +} diff --git a/languages/bruno/src/test/kotlin/io/vrap/codegen/languages/bruno/TestCodeGenerator.kt b/languages/bruno/src/test/kotlin/io/vrap/codegen/languages/bruno/TestCodeGenerator.kt new file mode 100644 index 000000000..7d8eb1270 --- /dev/null +++ b/languages/bruno/src/test/kotlin/io/vrap/codegen/languages/bruno/TestCodeGenerator.kt @@ -0,0 +1,42 @@ +package io.vrap.codegen.languages.bruno + +import io.vrap.codegen.languages.bruno.model.BrunoBaseTypes +import io.vrap.codegen.languages.bruno.model.BrunoModelModule +import io.vrap.rmf.codegen.CodeGeneratorConfig +import io.vrap.rmf.codegen.di.RamlApiProvider +import io.vrap.rmf.codegen.di.RamlGeneratorComponent +import io.vrap.rmf.codegen.di.RamlGeneratorModule +import org.junit.jupiter.api.Test +import java.nio.file.Path +import java.nio.file.Paths + +class TestCodeGenerator { + companion object { + private val userProvidedOutputPath = System.getenv("OUTPUT_FOLDER") + private val userProvidedPath = System.getenv("TEST_RAML_FILE") + private val apiPath : Path = Paths.get(if (userProvidedPath == null) "../../api-spec/api.raml" else userProvidedPath) + private val outputFolder : Path = Paths.get(if (userProvidedOutputPath == null) "build/gensrc/bruno" else userProvidedOutputPath) + val apiProvider: RamlApiProvider = RamlApiProvider(apiPath) + val generatorConfig = CodeGeneratorConfig(basePackageName = "") + } + + @Test + fun generatePostmanModels() { + val generatorConfig = CodeGeneratorConfig( + basePackageName = "com/commercetools/importer", + outputFolder = Paths.get("$outputFolder") + ) + + val generatorModule = RamlGeneratorModule(apiProvider, generatorConfig, BrunoBaseTypes) + val generatorComponent = RamlGeneratorComponent(generatorModule, BrunoModelModule) + generatorComponent.generateFiles() + } + + private fun cleanGenTestFolder() { + cleanFolder("build/gensrc") + } + + private fun cleanFolder(path: String) { + Paths.get(path).toFile().deleteRecursively() + } +} diff --git a/settings.gradle b/settings.gradle index a4f52ca09..b0dabf183 100644 --- a/settings.gradle +++ b/settings.gradle @@ -12,6 +12,7 @@ include 'languages:typescript' include 'languages:python' include 'languages:go' include 'languages:ramldoc' +include 'languages:bruno' include 'ctp-validators' include 'tools:cli-application' diff --git a/tools/cli-application/build.gradle b/tools/cli-application/build.gradle index 63e9f9e9a..b0b64bbbe 100644 --- a/tools/cli-application/build.gradle +++ b/tools/cli-application/build.gradle @@ -88,6 +88,7 @@ dependencies { implementation project(':languages:csharp') implementation project(':languages:ramldoc') implementation project(':languages:oas') + implementation project(':languages:bruno') api project(':ctp-validators') api swagger.swagger_parser api swagger.swagger_converter diff --git a/tools/cli-application/src/main/kotlin/io/vrap/rmf/codegen/cli/GenerateSubcommand.kt b/tools/cli-application/src/main/kotlin/io/vrap/rmf/codegen/cli/GenerateSubcommand.kt index 132124cee..79c54fa0e 100644 --- a/tools/cli-application/src/main/kotlin/io/vrap/rmf/codegen/cli/GenerateSubcommand.kt +++ b/tools/cli-application/src/main/kotlin/io/vrap/rmf/codegen/cli/GenerateSubcommand.kt @@ -5,6 +5,8 @@ import io.methvin.watcher.DirectoryWatcher import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.plugins.RxJavaPlugins import io.reactivex.rxjava3.schedulers.Schedulers +import io.vrap.codegen.languages.bruno.model.BrunoBaseTypes +import io.vrap.codegen.languages.bruno.model.BrunoModelModule import io.vrap.codegen.languages.csharp.CsharpBaseTypes import io.vrap.codegen.languages.csharp.client.builder.test.CsharpTestModule import io.vrap.codegen.languages.csharp.modules.CsharpClientBuilderModule @@ -77,9 +79,10 @@ enum class GenerationTarget { OAS, PYTHON_CLIENT, PLANTUML, - DOC_MARKDOWN + DOC_MARKDOWN, + BRUNO } -const val ValidTargets = "JAVA_CLIENT, JAVA_TEST, JAVA_QUERY_PREDICATES, TYPESCRIPT_CLIENT, TYPESCRIPT_TEST, CSHARP_CLIENT, CSHARP_TEST, CSHARP_QUERY_PREDICATES, PHP_CLIENT, PHP_BASE, PHP_TEST, POSTMAN, RAML_DOC, OAS, PYTHON_CLIENT, PLANTUML, DOC_MARKDOWN" +const val ValidTargets = "JAVA_CLIENT, JAVA_TEST, JAVA_QUERY_PREDICATES, TYPESCRIPT_CLIENT, TYPESCRIPT_TEST, CSHARP_CLIENT, CSHARP_TEST, CSHARP_QUERY_PREDICATES, PHP_CLIENT, PHP_BASE, PHP_TEST, POSTMAN, RAML_DOC, OAS, PYTHON_CLIENT, PLANTUML, DOC_MARKDOWN, BRUNO" @CommandLine.Command(name = "generate",description = ["Generate source code from a RAML specification."]) class GenerateSubcommand : Callable { @@ -272,6 +275,10 @@ class GenerateSubcommand : Callable { val generatorModule = RamlGeneratorModule(apiProvider, generatorConfig, PostmanBaseTypes, dataSink = sink) RamlGeneratorComponent(generatorModule, PostmanModelModule) } + GenerationTarget.BRUNO -> { + val generatorModule = RamlGeneratorModule(apiProvider, generatorConfig, BrunoBaseTypes, dataSink = sink) + RamlGeneratorComponent(generatorModule, BrunoModelModule) + } GenerationTarget.RAML_DOC -> { val ramlConfig = CodeGeneratorConfig( sharedPackage = generatorConfig.sharedPackage, From f2956325a63cd929578149a658fd4c1d27660ee9 Mon Sep 17 00:00:00 2001 From: Jens Schulze Date: Thu, 23 May 2024 12:18:39 +0200 Subject: [PATCH 02/20] cleanup generate bru file per method add base files --- .../bruno/model/BrunoMethodRenderer.kt | 111 +++++++++++++++ .../languages/bruno/model/BrunoModelModule.kt | 5 +- .../bruno/model/BrunoModuleRenderer.kt | 133 +++++++++++++----- .../languages/bruno/TestCodeGenerator.kt | 2 +- 4 files changed, 212 insertions(+), 39 deletions(-) create mode 100644 languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoMethodRenderer.kt diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoMethodRenderer.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoMethodRenderer.kt new file mode 100644 index 000000000..ae5f905f6 --- /dev/null +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoMethodRenderer.kt @@ -0,0 +1,111 @@ +package io.vrap.codegen.languages.bruno.model + +import com.fasterxml.jackson.core.JsonProcessingException +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.module.SimpleModule +import com.google.common.collect.Lists +import io.vrap.codegen.languages.extensions.* +import io.vrap.rmf.codegen.io.TemplateFile +import io.vrap.rmf.codegen.rendering.FileProducer +import io.vrap.rmf.codegen.rendering.MethodRenderer +import io.vrap.rmf.codegen.rendering.utils.escapeAll +import io.vrap.rmf.codegen.rendering.utils.keepAngleIndent +import io.vrap.rmf.codegen.rendering.utils.keepIndentation +import io.vrap.rmf.codegen.types.VrapObjectType +import io.vrap.rmf.codegen.types.VrapTypeProvider +import io.vrap.rmf.raml.model.modules.Api +import io.vrap.rmf.raml.model.resources.Method +import io.vrap.rmf.raml.model.resources.Resource +import io.vrap.rmf.raml.model.types.* +import io.vrap.rmf.raml.model.types.Annotation +import org.eclipse.emf.ecore.EObject + +class BrunoMethodRenderer constructor(val api: Api, override val vrapTypeProvider: VrapTypeProvider) : EObjectExtensions, FileProducer { + + val offset = 1 + + fun allResourceMethods(): List = api.allContainedResources.flatMap { it.methods } + + override fun produceFiles(): List { + return methods(api) + } + + fun methods(api: Api): List { + return allResourceMethods().mapIndexed { index, method -> render(index, method) } + } + + + fun render(index: Int, type: Method): TemplateFile { + + val content = """ + |meta { + | name: ByProjectKeyGet + | type: http + | seq: ${index + offset} + |} + """.trimMargin().keepAngleIndent() + + val relativePath = type.resource().fullUri.template + "/" + type.toRequestName() + ".bru" + + return TemplateFile( + relativePath = relativePath, + content = content + ) + } + + fun Instance.toJson(): String { + var example = "" + val mapper = ObjectMapper() + + val module = SimpleModule() + module.addSerializer(ObjectInstance::class.java, ObjectInstanceSerializer()) + module.addSerializer(ArrayInstance::class.java, InstanceSerializer()) + module.addSerializer(IntegerInstance::class.java, InstanceSerializer()) + module.addSerializer(BooleanInstance::class.java, InstanceSerializer()) + module.addSerializer(StringInstance::class.java, InstanceSerializer()) + module.addSerializer(NumberInstance::class.java, InstanceSerializer()) + mapper.registerModule(module) + + if (this is StringInstance) { + example = this.value + } else if (this is ObjectInstance) { + try { + example = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(this) + } catch (e: JsonProcessingException) { + } + + } + + return example + } + + fun Resource.testScript(param: String = ""): String { + return """ + |tests["Status code " + responseCode.code] = responseCode.code === 200 || responseCode.code === 201; + |var data = JSON.parse(responseBody); + |if(data.results && data.results[0] && data.results[0].id && data.results[0].version){ + | pm.environment.set("${this.resourcePathName.singularize()}-id", data.results[0].id); + | pm.environment.set("${this.resourcePathName.singularize()}-version", data.results[0].version); + |} + |if(data.results && data.results[0] && data.results[0].key){ + | pm.environment.set("${this.resourcePathName.singularize()}-key", data.results[0].key); + |} + |if(data.version){ + | pm.environment.set("${this.resourcePathName.singularize()}-version", data.version); + |} + |if(data.id){ + | pm.environment.set("${this.resourcePathName.singularize()}-id", data.id); + |} + |if(data.key){ + | pm.environment.set("${this.resourcePathName.singularize()}-key", data.key); + |} + |${if (param.isNotEmpty()) """ + |if(data.${param}){ + | pm.environment.set("${this.resourcePathName.singularize()}-${param}", data.${param}); + |} + """.trimMargin() else ""} + """.trimMargin().split("\n").map { it.escapeJson().escapeAll() }.joinToString("\",\n\"", "\"", "\"") + } +} + + diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModelModule.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModelModule.kt index 3eac5f794..19c50d611 100644 --- a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModelModule.kt +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModelModule.kt @@ -1,15 +1,18 @@ package io.vrap.codegen.languages.bruno.model +import io.vrap.codegen.languages.extensions.deprecated import io.vrap.rmf.codegen.di.RamlGeneratorModule import io.vrap.rmf.codegen.di.Module import io.vrap.rmf.codegen.rendering.CodeGenerator import io.vrap.rmf.codegen.rendering.FileGenerator +import io.vrap.rmf.codegen.rendering.MethodGenerator object BrunoModelModule : Module { override fun configure(generatorModule: RamlGeneratorModule) = setOf( FileGenerator( setOf( - BrunoModuleRenderer(generatorModule.provideRamlModel(), generatorModule.vrapTypeProvider()) + BrunoModuleRenderer(generatorModule.provideRamlModel(), generatorModule.vrapTypeProvider()), + BrunoMethodRenderer(generatorModule.provideRamlModel(), generatorModule.vrapTypeProvider()) ) ) ) diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModuleRenderer.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModuleRenderer.kt index 8ecb60989..81a8fda3c 100644 --- a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModuleRenderer.kt +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModuleRenderer.kt @@ -1,5 +1,6 @@ package io.vrap.codegen.languages.bruno.model +import com.damnhandy.uri.template.UriTemplate import com.fasterxml.jackson.core.JsonProcessingException import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.module.SimpleModule @@ -17,54 +18,112 @@ class BrunoModuleRenderer constructor(val api: Api, override val vrapTypeProvide override fun produceFiles(): List { return listOf( - template(api), - collection(api) + brunoJson(api), + collectionBru(api), + clientCredentialsBru(api), + exampleEnvironment(api) ) } - private fun template(api: Api): TemplateFile { - return TemplateFile(relativePath = "template.json", + private fun exampleEnvironment(api: Api): TemplateFile { + val baseUri = when (val sdkBaseUri = api.getAnnotation("sdkBaseUri")?.value) { + is StringInstance -> sdkBaseUri.value + else -> api.baseUri.template + }.trimEnd('/') + return TemplateFile(relativePath = "environments/Example.bru", + content = """ + |vars { + | authUrl: ${api.oAuth2().uri().toString().trimEnd('/')} + | apiUrl: $baseUri + | projectKey: + |} + |vars:secret [ + | ctp_client_id, + | ctp_client_secret, + | ctp_access_token + |] + """.trimMargin().keepAngleIndent() + ) + } + + private fun brunoJson(api: Api): TemplateFile { + return TemplateFile(relativePath = "bruno.json", content = """ |{ - | "id": "5bb74f05-5e78-4aee-b59e-492c947bc160", - | "name": "${api.title}.template", - | "values": [ - | { - | "enabled": true, - | "key": "host", - | "value": "${api.baseUri.template.trimEnd('/')}", - | "type": "text" - | }, - | { - | "enabled": true, - | "key": "auth_url", - | "value": "https://${api.oAuth2().uri().host}", - | "type": "text" - | }, - | { - | "enabled": true, - | "key": "client_id", - | "value": "", - | "type": "text" - | }, - | { - | "enabled": true, - | "key": "client_secret", - | "value": "", - | "type": "text" - | }, - | { - | "enabled": true, - | "key": "ctp_access_token", - | "value": "", - | "type": "text" - | } + | "version": "1", + | "name": "${api.title}", + | "type": "collection", + | "ignore": [ + | "node_modules", + | ".git" | ] |} """.trimMargin() ) } + private fun collectionBru(api: Api): TemplateFile { + return TemplateFile(relativePath = "collection.bru", + content = """ + |auth { + | mode: bearer + |} + | + |auth:bearer { + | token: {{ctp_access_token}} + |} + """.trimMargin() + ) + } + + private fun clientCredentialsBru(api: Api): TemplateFile { + return TemplateFile(relativePath = "auth/clientCredentials.bru", + content = """ + |meta { + | name: Client Credentials + | type: http + | seq: 1 + |} + | + |post { + | url: {{authHost}} + | body: formUrlEncoded + | auth: basic + |} + | + |body:form-urlencoded { + | grant_type: client_credentials + |} + | + |auth:basic { + | username: {{ctp_client_id}} + | password: {{ctp_client_secret}} + |} + | + |script:post-response { + | var data = res.body; + | if(data.access_token){ + | bru.setEnvVar("ctp_access_token", data.access_token, true); + | } + | + | if (data.scope) { + | parts = data.scope.split(" "); + | parts = parts.filter(scope => scope.includes(":")).map(scope => scope.split(":")) + | if (parts.length > 0) { + | scopeParts = parts[0]; + | bru.setEnvVar("projectKey", scopeParts[1]); + | parts = parts.filter(scope => scope.length >= 3) + | if (parts.length > 0) { + | scopeParts = parts[0]; + | bru.setEnvVar("storeKey", scopeParts[2]); + | } + | } + | } + |} + """.trimMargin() + ) + } + private fun collection(api: Api): TemplateFile { return TemplateFile(relativePath = "collection.json", content = """ diff --git a/languages/bruno/src/test/kotlin/io/vrap/codegen/languages/bruno/TestCodeGenerator.kt b/languages/bruno/src/test/kotlin/io/vrap/codegen/languages/bruno/TestCodeGenerator.kt index 7d8eb1270..0696f40b9 100644 --- a/languages/bruno/src/test/kotlin/io/vrap/codegen/languages/bruno/TestCodeGenerator.kt +++ b/languages/bruno/src/test/kotlin/io/vrap/codegen/languages/bruno/TestCodeGenerator.kt @@ -21,7 +21,7 @@ class TestCodeGenerator { } @Test - fun generatePostmanModels() { + fun generateBrunoModels() { val generatorConfig = CodeGeneratorConfig( basePackageName = "com/commercetools/importer", outputFolder = Paths.get("$outputFolder") From 8b441ae4eac48f9f4accb42560a1de37870618c5 Mon Sep 17 00:00:00 2001 From: Jens Schulze Date: Thu, 23 May 2024 12:36:12 +0200 Subject: [PATCH 03/20] add dotenv sample file --- .../bruno/model/BrunoModuleRenderer.kt | 44 ++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModuleRenderer.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModuleRenderer.kt index 81a8fda3c..cdb2de336 100644 --- a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModuleRenderer.kt +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModuleRenderer.kt @@ -21,7 +21,10 @@ class BrunoModuleRenderer constructor(val api: Api, override val vrapTypeProvide brunoJson(api), collectionBru(api), clientCredentialsBru(api), - exampleEnvironment(api) + exampleEnvironment(api), + dotEnvEnvironment(api), + dotEnvSample(api), + gitIgnore() ) } @@ -42,6 +45,43 @@ class BrunoModuleRenderer constructor(val api: Api, override val vrapTypeProvide | ctp_client_secret, | ctp_access_token |] + """.trimMargin() + ) + } + + private fun gitIgnore(): TemplateFile { + return TemplateFile(relativePath = ".gitignore", + content = """ + |.env + """.trimMargin() + ) + } + private fun dotEnvSample(api: Api): TemplateFile { + return TemplateFile(relativePath = ".env.sample", + content = """ + |CTP_CLIENT_ID= + |CTP_CLIENT_SECRET= + """.trimMargin() + ) + } + + private fun dotEnvEnvironment(api: Api): TemplateFile { + val baseUri = when (val sdkBaseUri = api.getAnnotation("sdkBaseUri")?.value) { + is StringInstance -> sdkBaseUri.value + else -> api.baseUri.template + }.trimEnd('/') + return TemplateFile(relativePath = "environments/DotEnv.bru", + content = """ + |vars { + | authUrl: ${api.oAuth2().uri().toString().trimEnd('/')} + | apiUrl: $baseUri + | projectKey: + | ctp_client_id: {{process.env.CTP_CLIENT_ID}} + | ctp_client_secret: {{process.env.CTP_CLIENT_SECRET}} + |} + |vars:secret [ + | ctp_access_token + |] """.trimMargin().keepAngleIndent() ) } @@ -86,7 +126,7 @@ class BrunoModuleRenderer constructor(val api: Api, override val vrapTypeProvide |} | |post { - | url: {{authHost}} + | url: {{authUrl}} | body: formUrlEncoded | auth: basic |} From ad3b4a90094997a7a454027f89f6aab1e5592e87 Mon Sep 17 00:00:00 2001 From: Jens Schulze Date: Thu, 23 May 2024 12:47:35 +0200 Subject: [PATCH 04/20] assert auth success --- .../languages/bruno/model/BrunoModuleRenderer.kt | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModuleRenderer.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModuleRenderer.kt index cdb2de336..429b7d759 100644 --- a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModuleRenderer.kt +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModuleRenderer.kt @@ -141,12 +141,13 @@ class BrunoModuleRenderer constructor(val api: Api, override val vrapTypeProvide |} | |script:post-response { - | var data = res.body; - | if(data.access_token){ - | bru.setEnvVar("ctp_access_token", data.access_token, true); - | } + | if(res.status == 200) { + | var data = res.body; + | if(data.access_token){ + | bru.setEnvVar("ctp_access_token", data.access_token, true); + | } | - | if (data.scope) { + | if (data.scope) { | parts = data.scope.split(" "); | parts = parts.filter(scope => scope.includes(":")).map(scope => scope.split(":")) | if (parts.length > 0) { @@ -158,8 +159,13 @@ class BrunoModuleRenderer constructor(val api: Api, override val vrapTypeProvide | bru.setEnvVar("storeKey", scopeParts[2]); | } | } + | } | } |} + | + |assert { + | res.status: eq 200 + |} """.trimMargin() ) } From 08c58d02f003bd2108777d8d1b22839ffdfcdb4c Mon Sep 17 00:00:00 2001 From: Jens Schulze Date: Thu, 23 May 2024 13:12:03 +0200 Subject: [PATCH 05/20] use resource path name for folder names --- .../bruno/model/BrunoMethodRenderer.kt | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoMethodRenderer.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoMethodRenderer.kt index ae5f905f6..ad589e0c4 100644 --- a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoMethodRenderer.kt +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoMethodRenderer.kt @@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.module.SimpleModule import com.google.common.collect.Lists import io.vrap.codegen.languages.extensions.* +import io.vrap.rmf.codegen.firstUpperCase import io.vrap.rmf.codegen.io.TemplateFile import io.vrap.rmf.codegen.rendering.FileProducer import io.vrap.rmf.codegen.rendering.MethodRenderer @@ -39,13 +40,13 @@ class BrunoMethodRenderer constructor(val api: Api, override val vrapTypeProvide val content = """ |meta { - | name: ByProjectKeyGet + | name: "${type.displayName?.value ?: "${type.methodName} ${type.resource().toResourceName()}" }" | type: http | seq: ${index + offset} |} """.trimMargin().keepAngleIndent() - val relativePath = type.resource().fullUri.template + "/" + type.toRequestName() + ".bru" + val relativePath = methodResourcePath(type) + "/" + type.toRequestName() + ".bru" return TemplateFile( relativePath = relativePath, @@ -53,6 +54,26 @@ class BrunoMethodRenderer constructor(val api: Api, override val vrapTypeProvide ) } + + private fun methodResourcePath(method: Method): String { + var resourcePathes = resourcePathes(method.resource()) + + var directories = resourcePathes.map { it.displayName?.value ?: it.resourcePathName.firstUpperCase() } + return directories.joinToString("/") + } + + private fun resourcePathes(resource: Resource): List { + if (resource.parent is Resource) { + if (resource.resourcePathName == resource.parent.resourcePathName) { + return resourcePathes(resource.parent) + } + return resourcePathes(resource.parent).plus(resource) + } + return listOf(resource) + } + + + fun Instance.toJson(): String { var example = "" val mapper = ObjectMapper() From e9258968b14bde71b8bb00d5f743efbdba46a75c Mon Sep 17 00:00:00 2001 From: Andrea Zito Date: Thu, 23 May 2024 13:21:10 +0200 Subject: [PATCH 06/20] bruno request renderer --- .../bruno/model/AuthorizationRenderer.kt | 10 +-- .../codegen/languages/bruno/model/BrunoUrl.kt | 12 +-- .../languages/bruno/model/MethodRenderer.kt | 78 ++++++++----------- 3 files changed, 38 insertions(+), 62 deletions(-) diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/AuthorizationRenderer.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/AuthorizationRenderer.kt index 200764841..c2cb15b82 100644 --- a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/AuthorizationRenderer.kt +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/AuthorizationRenderer.kt @@ -255,15 +255,11 @@ private fun testAuthScript(): String { """.trimMargin() } +fun authType(): String = "bearer" fun auth(): String { return """ - |{ - | "type": "oauth2", - | "oauth2": { - | "accessToken": "{{ctp_access_token}}", - | "addTokenTo": "header", - | "tokenType": "Bearer" - | } + |auth:bearer { + | token: "{{ctp_access_token}}" |} """.trimMargin() } diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoUrl.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoUrl.kt index 2df18932d..ec927e254 100644 --- a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoUrl.kt +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoUrl.kt @@ -48,18 +48,12 @@ class BrunoUrl (private val resource: Resource, private val method: Method, val } fun query(): String { - return method.queryParameters.joinToString(",\n") { it.queryParam() } + return method.queryParameters.joinToString("\n") { it.queryParam() } } private fun QueryParameter.queryParam() : String { - return """ - |{ - | "key": "${this.name}", - | "value": "${this.defaultValue()}", - | "equals": true, - | "disabled": ${this.required.not()} - |} - """.trimMargin() + val disabled = if (this.required) "" else "~" + return "${disabled}${this.name}: ${this.defaultValue()}" } fun QueryParameter.defaultValue(): String { diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/MethodRenderer.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/MethodRenderer.kt index a1a129d99..088dc04c1 100644 --- a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/MethodRenderer.kt +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/MethodRenderer.kt @@ -17,51 +17,41 @@ class MethodRenderer { }} return """ |{ - | "name": "${method.displayName?.value ?: "${method.methodName} ${method.resource().toResourceName()}" }", - | "event": [ - | { - | "listen": "test", - | "script": { - | "type": "text/javascript", - | "exec": [ - | <<${method.resource().testScript()}>> - | ] - | } - | } - | ], - | "request": { - | "auth": - | <<${auth()}>>, - | "method": "${method.methodName}", - | "header": [ - | { - | "key": "Content-Type", - | "value": "application/json" - | } - | ], - | "url": { - | "raw": "${url.raw()}", - | "host": [ - | "{{host}}" - | ], - | "path": [ - | <<"${url.path()}">> - | ], - | "query": [ - | <<${url.query()}>> - | ] - | }, - | "description": "${method.description?.description()}", - | "body": { - | "mode": "raw", - | "raw": "${method.rawBody()}" - | } - | }, - | "response": [] + | meta { + | name: "${method.displayName?.value ?: "${method.methodName} ${method.resource().toResourceName()}" }" + | type: http + | seq: 1 + | } + | + | ${method.methodName} { + | url: "${url.raw()}" + | body: ${method.bodyType()} + | auth: ${authType()} + | } + | + | <<${auth()}>> + | + | <<${method.jsonBody()}>> + | + | query { + | <<${url.query()}>> + | } + | |} """.trimMargin() } + fun Method.bodyType(): String { + return if (this.getExample() != null) "json" else "none" + } + + fun Method.jsonBody(): String { + val s = this.getExample() + return if (s != null) { + "body:json ${s}" + } else "" + } + fun Method.getExample(): String? { val s = this.bodies?. getOrNull(0)?. @@ -69,10 +59,6 @@ class MethodRenderer { examples?. getOrNull(0)?. value - return StringEscapeUtils.escapeJson(s?.toJson()) - } - - fun Method.rawBody(): String { - return this.getExample()?.escapeAll()?:"" + return s?.toJson() } } From c875ed37874ab790ef33fa9ff863aa1c477e7f61 Mon Sep 17 00:00:00 2001 From: Jens Schulze Date: Thu, 23 May 2024 14:28:58 +0200 Subject: [PATCH 07/20] add action file renderer --- .../bruno/model/BrunoActionRenderer.kt | 166 ++++++++++++++++++ .../languages/bruno/model/BrunoModelModule.kt | 3 +- 2 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoActionRenderer.kt diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoActionRenderer.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoActionRenderer.kt new file mode 100644 index 000000000..1a451223a --- /dev/null +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoActionRenderer.kt @@ -0,0 +1,166 @@ +package io.vrap.codegen.languages.bruno.model + +import com.fasterxml.jackson.core.JsonProcessingException +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.module.SimpleModule +import com.google.common.collect.Lists +import io.vrap.codegen.languages.extensions.* +import io.vrap.rmf.codegen.firstUpperCase +import io.vrap.rmf.codegen.io.TemplateFile +import io.vrap.rmf.codegen.rendering.FileProducer +import io.vrap.rmf.codegen.rendering.MethodRenderer +import io.vrap.rmf.codegen.rendering.utils.escapeAll +import io.vrap.rmf.codegen.rendering.utils.keepAngleIndent +import io.vrap.rmf.codegen.rendering.utils.keepIndentation +import io.vrap.rmf.codegen.types.VrapObjectType +import io.vrap.rmf.codegen.types.VrapTypeProvider +import io.vrap.rmf.raml.model.modules.Api +import io.vrap.rmf.raml.model.resources.HttpMethod +import io.vrap.rmf.raml.model.resources.Method +import io.vrap.rmf.raml.model.resources.Resource +import io.vrap.rmf.raml.model.types.* +import io.vrap.rmf.raml.model.types.Annotation +import io.vrap.rmf.raml.model.util.StringCaseFormat +import org.eclipse.emf.ecore.EObject + +class BrunoActionRenderer constructor(val api: Api, override val vrapTypeProvider: VrapTypeProvider) : EObjectExtensions, FileProducer { + + val offset = 1 + allResourceMethods().count() + + fun allResourceMethods(): List = api.allContainedResources.flatMap { it.methods } + fun allResources(): List = api.allContainedResources + + override fun produceFiles(): List { + return updateActions(api) + } + + fun updateActions(api: Api): List { + val updatableResources = api.allContainedResources.filter { it.getAnnotation("updateable") != null } + + return updatableResources.flatMap { resourceUpdates(it) } + } + + fun resourceUpdates(resource: Resource): List { + val updateMethod = resource.getUpdateMethod() + return updateMethod?.getActions()?.filterNot { objType -> objType.deprecated() }?.mapIndexed { index, objectType -> renderAction(resource, updateMethod, objectType, index) } ?: return emptyList() + } + + private fun renderAction(resource: Resource, method: Method, type: ObjectType, index: Int): TemplateFile { + val content = """ + |meta { + | name: "${type.discriminatorValue.firstUpperCase()}${if (type.markDeprecated()) " (deprecated)" else ""}" + | type: http + | seq: ${index + offset} + |} + """.trimMargin() + + val relativePath = methodResourcePath(method) + "/Update actions/" + type.discriminatorValue.firstUpperCase() + ".bru" + + return TemplateFile( + relativePath = relativePath, + content = content + ) + } + + private fun ObjectType.markDeprecated() : Boolean { + val anno = this.getAnnotation("markDeprecated") + return (anno != null && (anno.value as BooleanInstance).value) + } + + private fun Resource.getUpdateMethod(): Method? { + val byIdResource = this.resources.find { resource -> resource.relativeUri.template == "/{ID}" } ?: return null + + return byIdResource.getMethod(HttpMethod.POST) + } + + private fun Method.getActions(): List { + val body = this.getBody("application/json") ?: return emptyList() + + val actions = (body.type as ObjectType).getProperty("actions") ?: return emptyList() + + val actionsType = actions.type as ArrayType + val updateActions = if (actionsType.items is UnionType) { + (actionsType.items as UnionType).oneOf[0].subTypes + } else { + actionsType.items.subTypes + } + val actionItems = updateActions.map { action -> action as ObjectType }.sortedBy { action -> action.discriminatorValue } + return actionItems + } + + + private fun methodResourcePath(method: Method): String { + var resourcePathes = resourcePathes(method.resource()) + + var directories = resourcePathes.map { it.displayName?.value ?: it.resourcePathName.firstUpperCase() } + return directories.joinToString("/") + } + + private fun resourcePathes(resource: Resource): List { + if (resource.parent is Resource) { + if (resource.resourcePathName == resource.parent.resourcePathName) { + return resourcePathes(resource.parent) + } + return resourcePathes(resource.parent).plus(resource) + } + return listOf(resource) + } + + + + fun Instance.toJson(): String { + var example = "" + val mapper = ObjectMapper() + + val module = SimpleModule() + module.addSerializer(ObjectInstance::class.java, ObjectInstanceSerializer()) + module.addSerializer(ArrayInstance::class.java, InstanceSerializer()) + module.addSerializer(IntegerInstance::class.java, InstanceSerializer()) + module.addSerializer(BooleanInstance::class.java, InstanceSerializer()) + module.addSerializer(StringInstance::class.java, InstanceSerializer()) + module.addSerializer(NumberInstance::class.java, InstanceSerializer()) + mapper.registerModule(module) + + if (this is StringInstance) { + example = this.value + } else if (this is ObjectInstance) { + try { + example = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(this) + } catch (e: JsonProcessingException) { + } + + } + + return example + } + + fun Resource.testScript(param: String = ""): String { + return """ + |tests["Status code " + responseCode.code] = responseCode.code === 200 || responseCode.code === 201; + |var data = JSON.parse(responseBody); + |if(data.results && data.results[0] && data.results[0].id && data.results[0].version){ + | pm.environment.set("${this.resourcePathName.singularize()}-id", data.results[0].id); + | pm.environment.set("${this.resourcePathName.singularize()}-version", data.results[0].version); + |} + |if(data.results && data.results[0] && data.results[0].key){ + | pm.environment.set("${this.resourcePathName.singularize()}-key", data.results[0].key); + |} + |if(data.version){ + | pm.environment.set("${this.resourcePathName.singularize()}-version", data.version); + |} + |if(data.id){ + | pm.environment.set("${this.resourcePathName.singularize()}-id", data.id); + |} + |if(data.key){ + | pm.environment.set("${this.resourcePathName.singularize()}-key", data.key); + |} + |${if (param.isNotEmpty()) """ + |if(data.${param}){ + | pm.environment.set("${this.resourcePathName.singularize()}-${param}", data.${param}); + |} + """.trimMargin() else ""} + """.trimMargin().split("\n").map { it.escapeJson().escapeAll() }.joinToString("\",\n\"", "\"", "\"") + } +} + + diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModelModule.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModelModule.kt index 19c50d611..29b781e64 100644 --- a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModelModule.kt +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModelModule.kt @@ -12,7 +12,8 @@ object BrunoModelModule : Module { FileGenerator( setOf( BrunoModuleRenderer(generatorModule.provideRamlModel(), generatorModule.vrapTypeProvider()), - BrunoMethodRenderer(generatorModule.provideRamlModel(), generatorModule.vrapTypeProvider()) + BrunoMethodRenderer(generatorModule.provideRamlModel(), generatorModule.vrapTypeProvider()), + BrunoActionRenderer(generatorModule.provideRamlModel(), generatorModule.vrapTypeProvider()) ) ) ) From 882a64d1e4af18b7a4f21e3c3e84c98840b8f554 Mon Sep 17 00:00:00 2001 From: Martin Welgemoed Date: Thu, 23 May 2024 14:14:23 +0200 Subject: [PATCH 08/20] Generating commands with body --- .../bruno/model/BrunoMethodRenderer.kt | 60 ++++++++++++++++--- 1 file changed, 52 insertions(+), 8 deletions(-) diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoMethodRenderer.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoMethodRenderer.kt index ad589e0c4..bcb8bfa56 100644 --- a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoMethodRenderer.kt +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoMethodRenderer.kt @@ -19,6 +19,7 @@ import io.vrap.rmf.raml.model.resources.Method import io.vrap.rmf.raml.model.resources.Resource import io.vrap.rmf.raml.model.types.* import io.vrap.rmf.raml.model.types.Annotation +import io.vrap.rmf.raml.model.util.StringCaseFormat import org.eclipse.emf.ecore.EObject class BrunoMethodRenderer constructor(val api: Api, override val vrapTypeProvider: VrapTypeProvider) : EObjectExtensions, FileProducer { @@ -35,16 +36,9 @@ class BrunoMethodRenderer constructor(val api: Api, override val vrapTypeProvide return allResourceMethods().mapIndexed { index, method -> render(index, method) } } - fun render(index: Int, type: Method): TemplateFile { - val content = """ - |meta { - | name: "${type.displayName?.value ?: "${type.methodName} ${type.resource().toResourceName()}" }" - | type: http - | seq: ${index + offset} - |} - """.trimMargin().keepAngleIndent() + val content = renderStr(index, type).trimMargin().keepAngleIndent() val relativePath = methodResourcePath(type) + "/" + type.toRequestName() + ".bru" @@ -54,6 +48,56 @@ class BrunoMethodRenderer constructor(val api: Api, override val vrapTypeProvide ) } + fun renderStr(index: Int, method: Method): String { + val url = BrunoUrl(method.resource(), method) { resource, name -> when (name) { + "ID" -> resource.resourcePathName.singularize() + "-id" + "key" -> resource.resourcePathName.singularize() + "-key" + else -> StringCaseFormat.LOWER_HYPHEN_CASE.apply(name) + }} + return """ + |meta { + | name: ${method.displayName?.value ?: "${method.methodName} ${method.resource().toResourceName()}" } + | type: http + | seq: ${index + offset} + |} + | + |${method.methodName} { + | url: ${url.raw()} + | body: ${method.bodyType()} + | auth: inherit + |} + | + |<<${method.jsonBody()}>> + | + |query { + | <<${url.query()}>> + |} + """.trimMargin() + } + + fun Method.bodyType(): String { + return if (this.getExample() != null) "json" else "none" + } + + fun Method.jsonBody(): String { + val s = this.getExample() + return if (s != null) { + """|body:json { + | <<${s}>> + |} + """.trimMargin() + } else "" + } + + fun Method.getExample(): String? { + val s = this.bodies?. + getOrNull(0)?. + type?. + examples?. + getOrNull(0)?. + value + return s?.toJson() + } private fun methodResourcePath(method: Method): String { var resourcePathes = resourcePathes(method.resource()) From a397f4af66beae8055c711bb61e18e5078d5132b Mon Sep 17 00:00:00 2001 From: Martin Welgemoed Date: Thu, 23 May 2024 14:30:38 +0200 Subject: [PATCH 09/20] Script working --- .../bruno/model/BrunoMethodRenderer.kt | 55 +++++++++++-------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoMethodRenderer.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoMethodRenderer.kt index bcb8bfa56..c630cf9e9 100644 --- a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoMethodRenderer.kt +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoMethodRenderer.kt @@ -72,6 +72,14 @@ class BrunoMethodRenderer constructor(val api: Api, override val vrapTypeProvide |query { | <<${url.query()}>> |} + | + |script:post-response { + | <<${method.resource().testScript()}>> + |} + | + |assert { + | res.status: in [200, 201] + |} """.trimMargin() } @@ -146,30 +154,31 @@ class BrunoMethodRenderer constructor(val api: Api, override val vrapTypeProvide fun Resource.testScript(param: String = ""): String { return """ - |tests["Status code " + responseCode.code] = responseCode.code === 200 || responseCode.code === 201; - |var data = JSON.parse(responseBody); - |if(data.results && data.results[0] && data.results[0].id && data.results[0].version){ - | pm.environment.set("${this.resourcePathName.singularize()}-id", data.results[0].id); - | pm.environment.set("${this.resourcePathName.singularize()}-version", data.results[0].version); - |} - |if(data.results && data.results[0] && data.results[0].key){ - | pm.environment.set("${this.resourcePathName.singularize()}-key", data.results[0].key); - |} - |if(data.version){ - | pm.environment.set("${this.resourcePathName.singularize()}-version", data.version); + |var data = res.body; + |if(res.status == 200 || res.status == 201) { + | if(data.results && data.results[0] && data.results[0].id && data.results[0].version){ + | bru.setEnvVar("${this.resourcePathName.singularize()}-id", data.results[0].id); + | bru.setEnvVar("${this.resourcePathName.singularize()}-version", data.results[0].version); + | } + | if(data.results && data.results[0] && data.results[0].key){ + | bru.setEnvVar("${this.resourcePathName.singularize()}-key", data.results[0].key); + | } + | if(data.version){ + | bru.setEnvVar("${this.resourcePathName.singularize()}-version", data.version); + | } + | if(data.id){ + | bru.setEnvVar("${this.resourcePathName.singularize()}-id", data.id); + | } + | if(data.key){ + | bru.setEnvVar("${this.resourcePathName.singularize()}-key", data.key); + | } + | ${if (param.isNotEmpty()) """ + | if(data.${param}){ + | bru.setEnvVar("${this.resourcePathName.singularize()}-${param}", data.${param}); + | } + |""".trimMargin() else ""} |} - |if(data.id){ - | pm.environment.set("${this.resourcePathName.singularize()}-id", data.id); - |} - |if(data.key){ - | pm.environment.set("${this.resourcePathName.singularize()}-key", data.key); - |} - |${if (param.isNotEmpty()) """ - |if(data.${param}){ - | pm.environment.set("${this.resourcePathName.singularize()}-${param}", data.${param}); - |} - """.trimMargin() else ""} - """.trimMargin().split("\n").map { it.escapeJson().escapeAll() }.joinToString("\",\n\"", "\"", "\"") + """.trimMargin() } } From fda4b4ca3d3c2b68173269c0692bbd60d3f6d288 Mon Sep 17 00:00:00 2001 From: Martin Welgemoed Date: Thu, 23 May 2024 14:38:45 +0200 Subject: [PATCH 10/20] Auth working --- .../vrap/codegen/languages/bruno/model/BrunoModuleRenderer.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModuleRenderer.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModuleRenderer.kt index 429b7d759..266a88e96 100644 --- a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModuleRenderer.kt +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModuleRenderer.kt @@ -126,7 +126,7 @@ class BrunoModuleRenderer constructor(val api: Api, override val vrapTypeProvide |} | |post { - | url: {{authUrl}} + | url: {{auth_url}}${api.oAuth2().uri().path}?grant_type=client_credentials | body: formUrlEncoded | auth: basic |} From 7d1de0d2a61181a6e068933cc10dd68b3f277d2e Mon Sep 17 00:00:00 2001 From: Martin Welgemoed Date: Thu, 23 May 2024 14:56:05 +0200 Subject: [PATCH 11/20] Fixed script --- .../bruno/model/BrunoActionRenderer.kt | 139 ++++++++++++++---- .../bruno/model/BrunoMethodRenderer.kt | 4 +- 2 files changed, 116 insertions(+), 27 deletions(-) diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoActionRenderer.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoActionRenderer.kt index 1a451223a..a172f1e5b 100644 --- a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoActionRenderer.kt +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoActionRenderer.kt @@ -3,6 +3,7 @@ package io.vrap.codegen.languages.bruno.model import com.fasterxml.jackson.core.JsonProcessingException import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.module.SimpleModule +import com.fasterxml.jackson.databind.node.ObjectNode import com.google.common.collect.Lists import io.vrap.codegen.languages.extensions.* import io.vrap.rmf.codegen.firstUpperCase @@ -22,6 +23,7 @@ import io.vrap.rmf.raml.model.types.* import io.vrap.rmf.raml.model.types.Annotation import io.vrap.rmf.raml.model.util.StringCaseFormat import org.eclipse.emf.ecore.EObject +import java.io.IOException class BrunoActionRenderer constructor(val api: Api, override val vrapTypeProvider: VrapTypeProvider) : EObjectExtensions, FileProducer { @@ -46,13 +48,41 @@ class BrunoActionRenderer constructor(val api: Api, override val vrapTypeProvide } private fun renderAction(resource: Resource, method: Method, type: ObjectType, index: Int): TemplateFile { + val url = BrunoUrl(method.resource(), method) { resource, name -> when (name) { + "ID" -> resource.resourcePathName.singularize() + "-id" + "key" -> resource.resourcePathName.singularize() + "-key" + else -> StringCaseFormat.LOWER_HYPHEN_CASE.apply(name) + }} + val actionBody = resource.actionExample(type) val content = """ |meta { - | name: "${type.discriminatorValue.firstUpperCase()}${if (type.markDeprecated()) " (deprecated)" else ""}" + | name: ${type.discriminatorValue.firstUpperCase()}${if (type.markDeprecated()) " (deprecated)" else ""} | type: http | seq: ${index + offset} |} - """.trimMargin() + | + |${method.methodName} { + | url: ${url.raw()} + | body: ${method.bodyType()} + | auth: inherit + |} + | + |body:json { + | <<${actionBody}>> + |} + | + |query { + | <<${url.query()}>> + |} + | + |script:post-response { + | <<${method.resource().testScript()}>> + |} + | + |assert { + | res.status: in [200, 201] + |} + """.trimMargin().keepAngleIndent() val relativePath = methodResourcePath(method) + "/Update actions/" + type.discriminatorValue.firstUpperCase() + ".bru" @@ -62,6 +92,64 @@ class BrunoActionRenderer constructor(val api: Api, override val vrapTypeProvide ) } + fun Method.getExample(): String? { + val s = this.bodies?. + getOrNull(0)?. + type?. + examples?. + getOrNull(0)?. + value + return s?.toJson() + } + + fun Method.bodyType(): String { + return if (this.getExample() != null) "json" else "none" + } + + private fun Resource.actionExample(type: ObjectType): String { + val example = getExample(type) + return """ + |{ + | "version": {{${this.resourcePathName.singularize()}-version}}, + | "actions": [ + | <<${if (example.isNullOrEmpty().not()) example else """ + | |{ + | | "action": "${type.discriminatorValue}" + | |}""".trimMargin()}>> + | ] + |} + """.trimMargin().keepAngleIndent() + } + + private fun getExample(type: ObjectType): String? { + var example: String? = null + var instance: Instance? = null + + if (type.getAnnotation("postman-example") != null) { + instance = type.getAnnotation("postman-example").value + } else if (type.examples.size > 0) { + instance = type.examples[0].value + } + + if (instance != null) { + example = instance.toJson() + try { + val mapper = ObjectMapper() + val nodes = mapper.readTree(example) as ObjectNode + nodes.put("action", type.discriminatorValue) + + example = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(nodes) + .split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray().map { s -> " $s" } + .joinToString("\n") + .trim { it <= ' ' } + } catch (e: IOException) { + } + + } + + return example + } + private fun ObjectType.markDeprecated() : Boolean { val anno = this.getAnnotation("markDeprecated") return (anno != null && (anno.value as BooleanInstance).value) @@ -136,30 +224,31 @@ class BrunoActionRenderer constructor(val api: Api, override val vrapTypeProvide fun Resource.testScript(param: String = ""): String { return """ - |tests["Status code " + responseCode.code] = responseCode.code === 200 || responseCode.code === 201; - |var data = JSON.parse(responseBody); - |if(data.results && data.results[0] && data.results[0].id && data.results[0].version){ - | pm.environment.set("${this.resourcePathName.singularize()}-id", data.results[0].id); - | pm.environment.set("${this.resourcePathName.singularize()}-version", data.results[0].version); - |} - |if(data.results && data.results[0] && data.results[0].key){ - | pm.environment.set("${this.resourcePathName.singularize()}-key", data.results[0].key); - |} - |if(data.version){ - | pm.environment.set("${this.resourcePathName.singularize()}-version", data.version); - |} - |if(data.id){ - | pm.environment.set("${this.resourcePathName.singularize()}-id", data.id); - |} - |if(data.key){ - | pm.environment.set("${this.resourcePathName.singularize()}-key", data.key); - |} - |${if (param.isNotEmpty()) """ - |if(data.${param}){ - | pm.environment.set("${this.resourcePathName.singularize()}-${param}", data.${param}); + |var data = res.body; + |if(res.status == 200 || res.status == 201) { + | if(data.results && data.results[0] && data.results[0].id && data.results[0].version){ + | bru.setEnvVar("${this.resourcePathName.singularize()}-id", data.results[0].id); + | bru.setEnvVar("${this.resourcePathName.singularize()}-version", data.results[0].version); + | } + | if(data.results && data.results[0] && data.results[0].key){ + | bru.setEnvVar("${this.resourcePathName.singularize()}-key", data.results[0].key); + | } + | if(data.version){ + | bru.setEnvVar("${this.resourcePathName.singularize()}-version", data.version); + | } + | if(data.id){ + | bru.setEnvVar("${this.resourcePathName.singularize()}-id", data.id); + | } + | if(data.key){ + | bru.setEnvVar("${this.resourcePathName.singularize()}-key", data.key); + | } + | ${if (param.isNotEmpty()) """ + | if(data.${param}){ + | bru.setEnvVar("${this.resourcePathName.singularize()}-${param}", data.${param}); + | } + |""".trimMargin() else ""} |} - """.trimMargin() else ""} - """.trimMargin().split("\n").map { it.escapeJson().escapeAll() }.joinToString("\",\n\"", "\"", "\"") + """.trimMargin() } } diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoMethodRenderer.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoMethodRenderer.kt index c630cf9e9..8ce38a2cd 100644 --- a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoMethodRenderer.kt +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoMethodRenderer.kt @@ -70,11 +70,11 @@ class BrunoMethodRenderer constructor(val api: Api, override val vrapTypeProvide |<<${method.jsonBody()}>> | |query { - | <<${url.query()}>> + | <<${url.query()}>> |} | |script:post-response { - | <<${method.resource().testScript()}>> + | <<${method.resource().testScript()}>> |} | |assert { From 4d32fc4d10adb61c0d2f0479b8af4c5ceccdc201 Mon Sep 17 00:00:00 2001 From: Andrea Zito Date: Thu, 23 May 2024 15:04:37 +0200 Subject: [PATCH 12/20] clean-up bruno generator --- .../languages/bruno/model/ActionRenderer.kt | 176 ------------ .../bruno/model/AuthorizationRenderer.kt | 269 ------------------ .../bruno/model/BrunoModuleRenderer.kt | 55 ---- .../languages/bruno/model/MethodRenderer.kt | 64 ----- .../languages/bruno/model/ResourceRenderer.kt | 38 --- 5 files changed, 602 deletions(-) delete mode 100644 languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/ActionRenderer.kt delete mode 100644 languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/AuthorizationRenderer.kt delete mode 100644 languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/MethodRenderer.kt delete mode 100644 languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/ResourceRenderer.kt diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/ActionRenderer.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/ActionRenderer.kt deleted file mode 100644 index ab8aa6411..000000000 --- a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/ActionRenderer.kt +++ /dev/null @@ -1,176 +0,0 @@ -package io.vrap.codegen.languages.bruno.model - -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.databind.node.ObjectNode -import io.vrap.codegen.languages.extensions.resource -import io.vrap.rmf.codegen.firstUpperCase -import io.vrap.rmf.codegen.rendering.utils.escapeAll -import io.vrap.rmf.codegen.rendering.utils.keepAngleIndent -import io.vrap.rmf.raml.model.resources.HttpMethod -import io.vrap.rmf.raml.model.resources.Method -import io.vrap.rmf.raml.model.resources.Resource -import io.vrap.rmf.raml.model.types.* -import io.vrap.rmf.raml.model.util.StringCaseFormat -import java.io.IOException -import java.util.* - -class ActionRenderer { - fun render(resource: Resource): List { - if (resource.getAnnotation("updateable") == null) return emptyList() - - val updateMethod = resource.getUpdateMethod() - - val actions = updateMethod?.getActions()?.filterNot { objType -> objType.deprecated() }?.map { renderAction(resource, updateMethod, it) } ?: return emptyList() - - return listOf(""" - |{ - | "name": "Update actions", - | "item": [ - | <<${actions.joinToString(",\n") }>> - | ] - |} - """.trimMargin()) - } - - private fun renderAction(resource: Resource, method: Method, type: ObjectType): String { - val url = BrunoUrl(method.resource(), method) { resource, name -> when (name) { - "ID" -> resource.resourcePathName.singularize() + "-id" - "key" -> resource.resourcePathName.singularize() + "-key" - else -> StringCaseFormat.LOWER_HYPHEN_CASE.apply(name) - }} - return """ - |{ - | "name": "${type.discriminatorValue.firstUpperCase()}${if (type.markDeprecated()) " (deprecated)" else ""}", - | "event": [ - | { - | "listen": "test", - | "script": { - | "type": "text/javascript", - | "exec": [ - | <<${resource.testScript()}>> - | ] - | } - | } - | ], - | "request": { - | "auth": - | <<${auth()}>>, - | "method": "${method.methodName}", - | "body": { - | "mode": "raw", - | "raw": "${resource.actionExample(type).escapeJson().escapeAll()}" - | }, - | "header": [ - | { - | "key": "Content-Type", - | "value": "application/json" - | } - | ], - | "url": { - | "raw": "${url.raw()}", - | "host": [ - | "{{host}}" - | ], - | "path": [ - | <<"${url.path()}">> - | ], - | "query": [ - | <<${url.query()}>> - | ] - | }, - | "description": "${method.description()}" - | }, - | "response": [] - |} - """.trimMargin() - } - - private fun ObjectType.deprecated() : Boolean { - val anno = this.getAnnotation("deprecated") - return (anno != null && (anno.value as BooleanInstance).value) - } - - private fun ObjectType.markDeprecated() : Boolean { - val anno = this.getAnnotation("markDeprecated") - return (anno != null && (anno.value as BooleanInstance).value) - } - - - private fun Resource.actionExample(type: ObjectType): String { - val example = getExample(type) - return """ - |{ - | "version": {{${this.resourcePathName.singularize()}-version}}, - | "actions": [ - | <<${if (example.isNullOrEmpty().not()) example else """ - | |{ - | | "action": "${type.discriminatorValue}" - | |}""".trimMargin()}>> - | ] - |} - """.trimMargin().keepAngleIndent() - } - - private fun getExample(type: ObjectType): String? { - var example: String? = null - var instance: Instance? = null - - if (type.getAnnotation("postman-example") != null) { - instance = type.getAnnotation("postman-example").value - } else if (type.examples.size > 0) { - instance = type.examples[0].value - } - - if (instance != null) { - example = instance.toJson() - try { - val mapper = ObjectMapper() - val nodes = mapper.readTree(example) as ObjectNode - nodes.put("action", type.discriminatorValue) - - example = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(nodes) - .split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray().map { s -> " $s" } - .joinToString("\n") - .trim { it <= ' ' } - } catch (e: IOException) { - } - - } - - return example - } - -// private fun getTestScript(type: ObjectType): String? { -// val t = type.getAnnotation("postman-test-script") -// if (t != null) return (t.value as StringInstance).value -// -// return null -// } - - private fun Resource.getUpdateMethod(): Method? { - val byIdResource = this.resources.find { resource -> resource.relativeUri.template == "/{ID}" } ?: return null - - return byIdResource.getMethod(HttpMethod.POST) - } - - private fun Method.getActions(): List { - val body = this.getBody("application/json") ?: return emptyList() - - val actions = (body.type as ObjectType).getProperty("actions") ?: return emptyList() - - val actionsType = actions.type as ArrayType - val updateActions = if (actionsType.items is UnionType) { - (actionsType.items as UnionType).oneOf[0].subTypes - } else { - actionsType.items.subTypes - } - val actionItems = updateActions.map { action -> action as ObjectType }.sortedBy { action -> action.discriminatorValue } - return actionItems - } - - fun Method.description(): String { - val name = StringCaseFormat.UPPER_CAMEL_CASE.apply(Optional.ofNullable(this.resource().displayName).map { it.value }.orElse(this.resource().resourcePathName)) - val description = Optional.ofNullable(this.description).map { it.value }.orElse(this.method.getName() + " " + name) - return description.escapeJson().escapeAll() - } -} diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/AuthorizationRenderer.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/AuthorizationRenderer.kt deleted file mode 100644 index c2cb15b82..000000000 --- a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/AuthorizationRenderer.kt +++ /dev/null @@ -1,269 +0,0 @@ -package io.vrap.codegen.languages.bruno.model - -import io.vrap.rmf.codegen.rendering.utils.escapeAll -import io.vrap.rmf.codegen.rendering.utils.keepAngleIndent -import io.vrap.rmf.raml.model.security.OAuth20Settings - -fun authorization(oauth: OAuth20Settings): String { - return """ - |{ - | "name": "Authorization", - | "description": "Authorization", - | "item": [ - | { - | "name": "Obtain access token", - | "event": [ - | { - | "listen": "test", - | "script": { - | "type": "text/javascript", - | "exec": [ - | <<${testAuthScript().jScript()}>> - | ] - | } - | } - | ], - | "request": { - | "auth": { - | "type": "basic", - | "basic": { - | "username": "{{client_id}}", - | "password": "{{client_secret}}" - | } - | }, - | "method": "POST", - | "header": [], - | "body": { - | "mode": "raw", - | "raw": "" - | }, - | "url": { - | "raw": "{{auth_url}}${oauth.uri().path}?grant_type=client_credentials", - | "host": [ - | "{{auth_url}}" - | ], - | "path": [ - | "oauth", - | "token" - | ], - | "query": [ - | { - | "key": "grant_type", - | "value": "client_credentials", - | "equals": true - | } - | ] - | }, - | "description": "Use this request to obtain an access token for your commercetools platform project via Client Credentials Flow. As a prerequisite you must have filled out environment variables in Postman for projectKey, client_id and client_secret to use this." - | }, - | "response": [] - | }, - | { - | "name": "Obtain access token through password flow", - | "event": [ - | { - | "listen": "test", - | "script": { - | "type": "text/javascript", - | "exec": [ - | <<${testAuthScript().jScript()}>> - | ] - | } - | } - | ], - | "request": { - | "auth": { - | "type": "basic", - | "basic": { - | "username": "{{client_id}}", - | "password": "{{client_secret}}" - | } - | }, - | "method": "POST", - | "header": [ - | { - | "key": "", - | "value": "", - | "disabled": true - | } - | ], - | "body": { - | "mode": "raw", - | "raw": "" - | }, - | "url": { - | "raw": "{{auth_url}}/oauth/{{project-key}}/customers/token?grant_type=password&username={{user_email}}&password={{user_password}}", - | "host": [ - | "{{auth_url}}" - | ], - | "path": [ - | "oauth", - | "{{project-key}}", - | "customers", - | "token" - | ], - | "query": [ - | { - | "key": "grant_type", - | "value": "password", - | "equals": true - | }, - | { - | "key": "username", - | "value": "", - | "equals": true - | }, - | { - | "key": "password", - | "value": "", - | "equals": true - | } - | ] - | }, - | "description": "Use this request to obtain an access token for your commercetools platform project via Password Flow. As a prerequisite you must have filled out environment variables in Postman for projectKey, client_id, client_secret, user_email and user_password to use this." - | }, - | "response": [] - | }, - | { - | "name": "Token for Anonymous Sessions", - | "event": [ - | { - | "listen": "test", - | "script": { - | "type": "text/javascript", - | "exec": [ - | <<${testAuthScript().jScript()}>> - | ] - | } - | } - | ], - | "request": { - | "auth": { - | "type": "basic", - | "basic": { - | "username": "{{client_id}}", - | "password": "{{client_secret}}" - | } - | }, - | "method": "POST", - | "header": [], - | "body": { - | "mode": "raw", - | "raw": "" - | }, - | "url": { - | "raw": "{{auth_url}}/oauth/{{project-key}}/anonymous/token?grant_type=client_credentials", - | "host": [ - | "{{auth_url}}" - | ], - | "path": [ - | "oauth", - | "{{project-key}}", - | "anonymous", - | "token" - | ], - | "query": [ - | { - | "key": "grant_type", - | "value": "client_credentials", - | "equals": true - | } - | ] - | }, - | "description": "Use this request to obtain an access token for a anonymous session. As a prerequisite you must have filled out environment variables in Postman for projectKey, client_id and client_secret to use this." - | }, - | "response": [] - | }, - | { - | "name": "Token Introspection", - | "event": [ - | { - | "listen": "test", - | "script": { - | "type": "text/javascript", - | "exec": [ - | <<${ "tests[\"Status code is 200\"] = responseCode.code === 200;".jScript() }>> - | ] - | } - | } - | ], - | "request": { - | "auth": { - | "type": "basic", - | "basic": { - | "username": "{{client_id}}", - | "password": "{{client_secret}}" - | } - | }, - | "method": "POST", - | "header": [ - | { - | "key": "Content-Type", - | "value": "application/json" - | } - | ], - | "body": { - | "mode": "raw", - | "raw": "" - | }, - | "url": { - | "raw": "{{auth_url}}/oauth/introspect?token={{ctp_access_token}}", - | "host": [ - | "{{auth_url}}" - | ], - | "path": [ - | "oauth", - | "introspect" - | ], - | "query": [ - | { - | "key": "token", - | "value": "{{ctp_access_token}}", - | "equals": true - | } - | ] - | }, - | "description": "Token introspection allows to determine the active state of an OAuth 2.0 access token and to determine meta-information about this accces token, such as the `scope`." - | }, - | "response": [] - | } - | ] - |} - """.trimMargin().keepAngleIndent().escapeAll() -} - -private fun testAuthScript(): String { - return """ - |tests["Status code is 200"] = responseCode.code === 200; - |var data = JSON.parse(responseBody); - |if(data.access_token){ - | pm.environment.set("ctp_access_token", data.access_token); - |} - |if (data.scope) { - | parts = data.scope.split(" "); - | parts = parts.filter(scope => scope.includes(":")).map(scope => scope.split(":")) - | if (parts.length > 0) { - | scopeParts = parts[0]; - | pm.environment.set("project-key", scopeParts[1]); - | parts = parts.filter(scope => scope.length >= 3) - | if (parts.length > 0) { - | scopeParts = parts[0]; - | pm.environment.set("store-key", scopeParts[2]); - | } - | } - |} - """.trimMargin() -} - -fun authType(): String = "bearer" -fun auth(): String { - return """ - |auth:bearer { - | token: "{{ctp_access_token}}" - |} - """.trimMargin() -} - -private fun String.jScript(): String { - return this.split("\n").map { it.escapeJson().escapeAll() }.joinToString("\",\n\"", "\"", "\""); -} diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModuleRenderer.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModuleRenderer.kt index 266a88e96..ab417435a 100644 --- a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModuleRenderer.kt +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModuleRenderer.kt @@ -169,61 +169,6 @@ class BrunoModuleRenderer constructor(val api: Api, override val vrapTypeProvide """.trimMargin() ) } - - private fun collection(api: Api): TemplateFile { - return TemplateFile(relativePath = "collection.json", - content = """ - |{ - | "info": { - | "_postman_id": "f367b534-c9ea-e7c5-1f46-7a27dc6a30ba", - | "name": "${api.title}", - | "description": "${readme(api).escapeJson().escapeAll()}", - | "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json" - | }, - | "auth": - | <<${auth()}>>, - | "item": [ - | <<${authorization(api.oAuth2())}>>, - | <<${api.resources.joinToString(",") { ResourceRenderer().render(it) }}>> - | ] - |} - """.trimMargin().keepAngleIndent()) - } - - private fun readme(api: Api): String { - return """ - # commercetools API Postman Collection - - This Postman collection contains examples of requests and responses for most endpoints and commands of the - ${api.title}. For every command the smallest possible payload is given. Please find optional - fields in the related official documentation. Additionally the collection provides example requests and - responses for specific tasks and more complex data models. - - ## Disclaimer - - This is not the official ${api.title} documentation. Please see [here](http://docs.commercetools.com/) - for a complete and approved documentation of the ${api.title}. - - ## How to use - - **:warning: Be aware that postman automatically synchronizes environment variables (including your API client credentials) to your workspace if logged in. - Use this collection only for development purposes and non-production projects.** - - To use this collection in Postman please perform the following steps: - - 1. Download and install the Postman Client - 2. Import the [collection.json](collection.json) and [template.json](template.json) in your postman application - 3. In the Merchant Center, create a new API Client and fill in the client credentials in your environment - 4. Obtain an access token by sending the "Authorization/Obtain access token" request at the bottom of the request list. Now you can use all other endpoints - - Feel free to clone and modify this collection to your needs. - - To automate frequent tasks the collection automatically manages commonly required values and parameters such - as resource ids, keys and versions in Postman environment variables for you. - - Please see http://docs.commercetools.com/ for further information about the commercetools Plattform. - """.trimIndent() - } } fun Instance.toJson(): String { diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/MethodRenderer.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/MethodRenderer.kt deleted file mode 100644 index 088dc04c1..000000000 --- a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/MethodRenderer.kt +++ /dev/null @@ -1,64 +0,0 @@ -package io.vrap.codegen.languages.bruno.model - -import io.vrap.codegen.languages.extensions.resource -import io.vrap.codegen.languages.extensions.toResourceName -import io.vrap.rmf.codegen.rendering.utils.escapeAll -import io.vrap.rmf.raml.model.resources.Method -import io.vrap.rmf.raml.model.util.StringCaseFormat -import org.apache.commons.text.StringEscapeUtils - -class MethodRenderer { - fun render(method: Method): String { - - val url = BrunoUrl(method.resource(), method) { resource, name -> when (name) { - "ID" -> resource.resourcePathName.singularize() + "-id" - "key" -> resource.resourcePathName.singularize() + "-key" - else -> StringCaseFormat.LOWER_HYPHEN_CASE.apply(name) - }} - return """ - |{ - | meta { - | name: "${method.displayName?.value ?: "${method.methodName} ${method.resource().toResourceName()}" }" - | type: http - | seq: 1 - | } - | - | ${method.methodName} { - | url: "${url.raw()}" - | body: ${method.bodyType()} - | auth: ${authType()} - | } - | - | <<${auth()}>> - | - | <<${method.jsonBody()}>> - | - | query { - | <<${url.query()}>> - | } - | - |} - """.trimMargin() - } - - fun Method.bodyType(): String { - return if (this.getExample() != null) "json" else "none" - } - - fun Method.jsonBody(): String { - val s = this.getExample() - return if (s != null) { - "body:json ${s}" - } else "" - } - - fun Method.getExample(): String? { - val s = this.bodies?. - getOrNull(0)?. - type?. - examples?. - getOrNull(0)?. - value - return s?.toJson() - } -} diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/ResourceRenderer.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/ResourceRenderer.kt deleted file mode 100644 index 1b2e5f058..000000000 --- a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/ResourceRenderer.kt +++ /dev/null @@ -1,38 +0,0 @@ -package io.vrap.codegen.languages.bruno.model - -import io.vrap.rmf.codegen.firstUpperCase -import io.vrap.rmf.raml.model.resources.Resource -import io.vrap.rmf.raml.model.types.BooleanInstance - -class ResourceRenderer { - fun render(resource: Resource): String { - - val items = resource.resources.filterNot { res -> res.deprecated() || res.markDeprecated() }.map { ResourceRenderer().render(it) } - .plus(resource.methods.map { MethodRenderer().render(it) }) - .plus(ActionRenderer().render(resource)) - - if ((resource.relativeUri.template.contains(resource.resourcePathName).not() && resource.relativeUriParameters.size > 0) || resource.resources.size == 0) { - return items.joinToString(",\n") - } - return """ - |{ - | "name": "${resource.displayName?.value ?: resource.resourcePathName.firstUpperCase()}", - | "description": "${resource.description?.description()}", - | "item": [ - | <<${items.joinToString(",\n")}>> - | ] - |} - """.trimMargin() - } - - private fun Resource.deprecated() : Boolean { - val anno = this.getAnnotation("deprecated") - return (anno != null && (anno.value as BooleanInstance).value) - } - - private fun Resource.markDeprecated() : Boolean { - val anno = this.getAnnotation("markDeprecated") - return (anno != null && (anno.value as BooleanInstance).value) - } - -} From f3ca936c9c35839a7bd9af4cc6768fbd42fa6687 Mon Sep 17 00:00:00 2001 From: Andrea Zito Date: Thu, 23 May 2024 15:23:49 +0200 Subject: [PATCH 13/20] Fixed environment vars --- .../codegen/languages/bruno/model/BrunoModuleRenderer.kt | 8 ++++---- .../io/vrap/codegen/languages/bruno/model/BrunoUrl.kt | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModuleRenderer.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModuleRenderer.kt index ab417435a..fe3f6ed07 100644 --- a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModuleRenderer.kt +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModuleRenderer.kt @@ -38,7 +38,7 @@ class BrunoModuleRenderer constructor(val api: Api, override val vrapTypeProvide |vars { | authUrl: ${api.oAuth2().uri().toString().trimEnd('/')} | apiUrl: $baseUri - | projectKey: + | project-key: |} |vars:secret [ | ctp_client_id, @@ -126,7 +126,7 @@ class BrunoModuleRenderer constructor(val api: Api, override val vrapTypeProvide |} | |post { - | url: {{auth_url}}${api.oAuth2().uri().path}?grant_type=client_credentials + | url: {{authUrl}} | body: formUrlEncoded | auth: basic |} @@ -152,11 +152,11 @@ class BrunoModuleRenderer constructor(val api: Api, override val vrapTypeProvide | parts = parts.filter(scope => scope.includes(":")).map(scope => scope.split(":")) | if (parts.length > 0) { | scopeParts = parts[0]; - | bru.setEnvVar("projectKey", scopeParts[1]); + | bru.setEnvVar("project-key", scopeParts[1]); | parts = parts.filter(scope => scope.length >= 3) | if (parts.length > 0) { | scopeParts = parts[0]; - | bru.setEnvVar("storeKey", scopeParts[2]); + | bru.setEnvVar("store-key", scopeParts[2]); | } | } | } diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoUrl.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoUrl.kt index ec927e254..2986390b9 100644 --- a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoUrl.kt +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoUrl.kt @@ -11,7 +11,7 @@ import org.eclipse.emf.ecore.EObject class BrunoUrl (private val resource: Resource, private val method: Method, val renameParam: Function2) { fun host(): String { - return "{{host}}" + return "{{apiUrl}}" } private fun transformUri(resource: Resource, uriComponent: UriTemplateComponent): String { From 69520df0cd550110e9ba27b78b3633dc43efcda4 Mon Sep 17 00:00:00 2001 From: Andrea Zito Date: Thu, 23 May 2024 15:53:59 +0200 Subject: [PATCH 14/20] refactor common rendering logic for bruno --- .../bruno/model/BrunoActionRenderer.kt | 74 +------------------ .../bruno/model/BrunoMethodRenderer.kt | 43 +---------- .../bruno/model/BrunoModuleRenderer.kt | 47 ++++++------ .../bruno/model/BrunoRequestRenderer.kt | 42 +++++++++++ 4 files changed, 70 insertions(+), 136 deletions(-) create mode 100644 languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoRequestRenderer.kt diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoActionRenderer.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoActionRenderer.kt index a172f1e5b..67972d421 100644 --- a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoActionRenderer.kt +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoActionRenderer.kt @@ -54,35 +54,8 @@ class BrunoActionRenderer constructor(val api: Api, override val vrapTypeProvide else -> StringCaseFormat.LOWER_HYPHEN_CASE.apply(name) }} val actionBody = resource.actionExample(type) - val content = """ - |meta { - | name: ${type.discriminatorValue.firstUpperCase()}${if (type.markDeprecated()) " (deprecated)" else ""} - | type: http - | seq: ${index + offset} - |} - | - |${method.methodName} { - | url: ${url.raw()} - | body: ${method.bodyType()} - | auth: inherit - |} - | - |body:json { - | <<${actionBody}>> - |} - | - |query { - | <<${url.query()}>> - |} - | - |script:post-response { - | <<${method.resource().testScript()}>> - |} - | - |assert { - | res.status: in [200, 201] - |} - """.trimMargin().keepAngleIndent() + val name = "${type.discriminatorValue.firstUpperCase()}${if (type.markDeprecated()) " (deprecated)" else ""}" + val content = BrunoRequestRenderer.renderRequest(name, method, url, actionBody, index + offset) val relativePath = methodResourcePath(method) + "/Update actions/" + type.discriminatorValue.firstUpperCase() + ".bru" @@ -92,20 +65,6 @@ class BrunoActionRenderer constructor(val api: Api, override val vrapTypeProvide ) } - fun Method.getExample(): String? { - val s = this.bodies?. - getOrNull(0)?. - type?. - examples?. - getOrNull(0)?. - value - return s?.toJson() - } - - fun Method.bodyType(): String { - return if (this.getExample() != null) "json" else "none" - } - private fun Resource.actionExample(type: ObjectType): String { val example = getExample(type) return """ @@ -221,35 +180,6 @@ class BrunoActionRenderer constructor(val api: Api, override val vrapTypeProvide return example } - - fun Resource.testScript(param: String = ""): String { - return """ - |var data = res.body; - |if(res.status == 200 || res.status == 201) { - | if(data.results && data.results[0] && data.results[0].id && data.results[0].version){ - | bru.setEnvVar("${this.resourcePathName.singularize()}-id", data.results[0].id); - | bru.setEnvVar("${this.resourcePathName.singularize()}-version", data.results[0].version); - | } - | if(data.results && data.results[0] && data.results[0].key){ - | bru.setEnvVar("${this.resourcePathName.singularize()}-key", data.results[0].key); - | } - | if(data.version){ - | bru.setEnvVar("${this.resourcePathName.singularize()}-version", data.version); - | } - | if(data.id){ - | bru.setEnvVar("${this.resourcePathName.singularize()}-id", data.id); - | } - | if(data.key){ - | bru.setEnvVar("${this.resourcePathName.singularize()}-key", data.key); - | } - | ${if (param.isNotEmpty()) """ - | if(data.${param}){ - | bru.setEnvVar("${this.resourcePathName.singularize()}-${param}", data.${param}); - | } - |""".trimMargin() else ""} - |} - """.trimMargin() - } } diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoMethodRenderer.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoMethodRenderer.kt index 8ce38a2cd..b7f8d414c 100644 --- a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoMethodRenderer.kt +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoMethodRenderer.kt @@ -54,47 +54,8 @@ class BrunoMethodRenderer constructor(val api: Api, override val vrapTypeProvide "key" -> resource.resourcePathName.singularize() + "-key" else -> StringCaseFormat.LOWER_HYPHEN_CASE.apply(name) }} - return """ - |meta { - | name: ${method.displayName?.value ?: "${method.methodName} ${method.resource().toResourceName()}" } - | type: http - | seq: ${index + offset} - |} - | - |${method.methodName} { - | url: ${url.raw()} - | body: ${method.bodyType()} - | auth: inherit - |} - | - |<<${method.jsonBody()}>> - | - |query { - | <<${url.query()}>> - |} - | - |script:post-response { - | <<${method.resource().testScript()}>> - |} - | - |assert { - | res.status: in [200, 201] - |} - """.trimMargin() - } - - fun Method.bodyType(): String { - return if (this.getExample() != null) "json" else "none" - } - - fun Method.jsonBody(): String { - val s = this.getExample() - return if (s != null) { - """|body:json { - | <<${s}>> - |} - """.trimMargin() - } else "" + val name = method.displayName?.value ?: "${method.methodName} ${method.resource().toResourceName()}" + return BrunoRequestRenderer.renderRequest(name, method, url, method.getExample(), index + offset) } fun Method.getExample(): String? { diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModuleRenderer.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModuleRenderer.kt index fe3f6ed07..1f24e832c 100644 --- a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModuleRenderer.kt +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModuleRenderer.kt @@ -199,28 +199,29 @@ fun Instance.toJson(): String { fun Resource.testScript(param: String = ""): String { return """ - |tests["Status code " + responseCode.code] = responseCode.code === 200 || responseCode.code === 201; - |var data = JSON.parse(responseBody); - |if(data.results && data.results[0] && data.results[0].id && data.results[0].version){ - | pm.environment.set("${this.resourcePathName.singularize()}-id", data.results[0].id); - | pm.environment.set("${this.resourcePathName.singularize()}-version", data.results[0].version); + |var data = res.body; + |if(res.status == 200 || res.status == 201) { + | if(data.results && data.results[0] && data.results[0].id && data.results[0].version){ + | bru.setEnvVar("${this.resourcePathName.singularize()}-id", data.results[0].id); + | bru.setEnvVar("${this.resourcePathName.singularize()}-version", data.results[0].version); + | } + | if(data.results && data.results[0] && data.results[0].key){ + | bru.setEnvVar("${this.resourcePathName.singularize()}-key", data.results[0].key); + | } + | if(data.version){ + | bru.setEnvVar("${this.resourcePathName.singularize()}-version", data.version); + | } + | if(data.id){ + | bru.setEnvVar("${this.resourcePathName.singularize()}-id", data.id); + | } + | if(data.key){ + | bru.setEnvVar("${this.resourcePathName.singularize()}-key", data.key); + | } + | ${if (param.isNotEmpty()) """ + | if(data.${param}){ + | bru.setEnvVar("${this.resourcePathName.singularize()}-${param}", data.${param}); + | } + |""".trimMargin() else ""} |} - |if(data.results && data.results[0] && data.results[0].key){ - | pm.environment.set("${this.resourcePathName.singularize()}-key", data.results[0].key); - |} - |if(data.version){ - | pm.environment.set("${this.resourcePathName.singularize()}-version", data.version); - |} - |if(data.id){ - | pm.environment.set("${this.resourcePathName.singularize()}-id", data.id); - |} - |if(data.key){ - | pm.environment.set("${this.resourcePathName.singularize()}-key", data.key); - |} - |${if (param.isNotEmpty()) """ - |if(data.${param}){ - | pm.environment.set("${this.resourcePathName.singularize()}-${param}", data.${param}); - |} - """.trimMargin() else ""} - """.trimMargin().split("\n").map { it.escapeJson().escapeAll() }.joinToString("\",\n\"", "\"", "\"") + """.trimMargin() } diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoRequestRenderer.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoRequestRenderer.kt new file mode 100644 index 000000000..3aa8422a2 --- /dev/null +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoRequestRenderer.kt @@ -0,0 +1,42 @@ +package io.vrap.codegen.languages.bruno.model + +import io.vrap.codegen.languages.extensions.resource +import io.vrap.rmf.codegen.rendering.utils.keepAngleIndent +import io.vrap.rmf.raml.model.resources.Method + +object BrunoRequestRenderer { + fun renderRequest(name: String, method: Method, url: BrunoUrl, body: String?, index: Int): String { + val bodyStr = if (body != null) """ + |body:json { + | <<${body}>> + |} + """.trimMargin().keepAngleIndent() else "" + return """ + |meta { + | name: $name + | type: http + | seq: $index + |} + | + |${method.methodName} { + | url: ${url.raw()} + | body: ${if (body != null) "json" else "none"} + | auth: inherit + |} + | + |<<${bodyStr}>> + | + |query { + | <<${url.query()}>> + |} + | + |script:post-response { + | <<${method.resource().testScript()}>> + |} + | + |assert { + | res.status: in [200, 201] + |} + """.trimMargin().keepAngleIndent() + } +} \ No newline at end of file From 94bc1a0103f8033a7171d2afa79dda312ca2f930 Mon Sep 17 00:00:00 2001 From: Martin Welgemoed Date: Thu, 23 May 2024 16:04:31 +0200 Subject: [PATCH 15/20] Removed dead code --- .../bruno/model/BrunoMethodRenderer.kt | 29 ------------------- 1 file changed, 29 deletions(-) diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoMethodRenderer.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoMethodRenderer.kt index b7f8d414c..31a6229d4 100644 --- a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoMethodRenderer.kt +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoMethodRenderer.kt @@ -112,35 +112,6 @@ class BrunoMethodRenderer constructor(val api: Api, override val vrapTypeProvide return example } - - fun Resource.testScript(param: String = ""): String { - return """ - |var data = res.body; - |if(res.status == 200 || res.status == 201) { - | if(data.results && data.results[0] && data.results[0].id && data.results[0].version){ - | bru.setEnvVar("${this.resourcePathName.singularize()}-id", data.results[0].id); - | bru.setEnvVar("${this.resourcePathName.singularize()}-version", data.results[0].version); - | } - | if(data.results && data.results[0] && data.results[0].key){ - | bru.setEnvVar("${this.resourcePathName.singularize()}-key", data.results[0].key); - | } - | if(data.version){ - | bru.setEnvVar("${this.resourcePathName.singularize()}-version", data.version); - | } - | if(data.id){ - | bru.setEnvVar("${this.resourcePathName.singularize()}-id", data.id); - | } - | if(data.key){ - | bru.setEnvVar("${this.resourcePathName.singularize()}-key", data.key); - | } - | ${if (param.isNotEmpty()) """ - | if(data.${param}){ - | bru.setEnvVar("${this.resourcePathName.singularize()}-${param}", data.${param}); - | } - |""".trimMargin() else ""} - |} - """.trimMargin() - } } From 5e5f4b4ef318ac40bd42f94202641a5048e88a9c Mon Sep 17 00:00:00 2001 From: Martin Welgemoed Date: Thu, 23 May 2024 16:15:21 +0200 Subject: [PATCH 16/20] Unused imports --- .../bruno/model/BrunoActionRenderer.kt | 13 ++---- .../bruno/model/BrunoMethodRenderer.kt | 40 ++++++++++++++----- .../languages/bruno/model/BrunoModelModule.kt | 2 - .../bruno/model/BrunoModuleRenderer.kt | 14 +++---- 4 files changed, 40 insertions(+), 29 deletions(-) diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoActionRenderer.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoActionRenderer.kt index 67972d421..ea619bcc4 100644 --- a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoActionRenderer.kt +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoActionRenderer.kt @@ -4,25 +4,18 @@ import com.fasterxml.jackson.core.JsonProcessingException import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.module.SimpleModule import com.fasterxml.jackson.databind.node.ObjectNode -import com.google.common.collect.Lists import io.vrap.codegen.languages.extensions.* import io.vrap.rmf.codegen.firstUpperCase import io.vrap.rmf.codegen.io.TemplateFile import io.vrap.rmf.codegen.rendering.FileProducer -import io.vrap.rmf.codegen.rendering.MethodRenderer -import io.vrap.rmf.codegen.rendering.utils.escapeAll import io.vrap.rmf.codegen.rendering.utils.keepAngleIndent -import io.vrap.rmf.codegen.rendering.utils.keepIndentation -import io.vrap.rmf.codegen.types.VrapObjectType import io.vrap.rmf.codegen.types.VrapTypeProvider import io.vrap.rmf.raml.model.modules.Api import io.vrap.rmf.raml.model.resources.HttpMethod import io.vrap.rmf.raml.model.resources.Method import io.vrap.rmf.raml.model.resources.Resource import io.vrap.rmf.raml.model.types.* -import io.vrap.rmf.raml.model.types.Annotation import io.vrap.rmf.raml.model.util.StringCaseFormat -import org.eclipse.emf.ecore.EObject import java.io.IOException class BrunoActionRenderer constructor(val api: Api, override val vrapTypeProvider: VrapTypeProvider) : EObjectExtensions, FileProducer { @@ -48,9 +41,9 @@ class BrunoActionRenderer constructor(val api: Api, override val vrapTypeProvide } private fun renderAction(resource: Resource, method: Method, type: ObjectType, index: Int): TemplateFile { - val url = BrunoUrl(method.resource(), method) { resource, name -> when (name) { - "ID" -> resource.resourcePathName.singularize() + "-id" - "key" -> resource.resourcePathName.singularize() + "-key" + val url = BrunoUrl(method.resource(), method) { methodResource, name -> when (name) { + "ID" -> methodResource.resourcePathName.singularize() + "-id" + "key" -> methodResource.resourcePathName.singularize() + "-key" else -> StringCaseFormat.LOWER_HYPHEN_CASE.apply(name) }} val actionBody = resource.actionExample(type) diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoMethodRenderer.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoMethodRenderer.kt index 31a6229d4..8429b9c53 100644 --- a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoMethodRenderer.kt +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoMethodRenderer.kt @@ -3,24 +3,17 @@ package io.vrap.codegen.languages.bruno.model import com.fasterxml.jackson.core.JsonProcessingException import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.module.SimpleModule -import com.google.common.collect.Lists import io.vrap.codegen.languages.extensions.* import io.vrap.rmf.codegen.firstUpperCase import io.vrap.rmf.codegen.io.TemplateFile import io.vrap.rmf.codegen.rendering.FileProducer -import io.vrap.rmf.codegen.rendering.MethodRenderer -import io.vrap.rmf.codegen.rendering.utils.escapeAll import io.vrap.rmf.codegen.rendering.utils.keepAngleIndent -import io.vrap.rmf.codegen.rendering.utils.keepIndentation -import io.vrap.rmf.codegen.types.VrapObjectType import io.vrap.rmf.codegen.types.VrapTypeProvider import io.vrap.rmf.raml.model.modules.Api import io.vrap.rmf.raml.model.resources.Method import io.vrap.rmf.raml.model.resources.Resource import io.vrap.rmf.raml.model.types.* -import io.vrap.rmf.raml.model.types.Annotation import io.vrap.rmf.raml.model.util.StringCaseFormat -import org.eclipse.emf.ecore.EObject class BrunoMethodRenderer constructor(val api: Api, override val vrapTypeProvider: VrapTypeProvider) : EObjectExtensions, FileProducer { @@ -29,10 +22,10 @@ class BrunoMethodRenderer constructor(val api: Api, override val vrapTypeProvide fun allResourceMethods(): List = api.allContainedResources.flatMap { it.methods } override fun produceFiles(): List { - return methods(api) + return methods() } - fun methods(api: Api): List { + private fun methods(): List { return allResourceMethods().mapIndexed { index, method -> render(index, method) } } @@ -112,6 +105,35 @@ class BrunoMethodRenderer constructor(val api: Api, override val vrapTypeProvide return example } + + fun Resource.testScript(param: String = ""): String { + return """ + |var data = res.body; + |if(res.status == 200 || res.status == 201) { + | if(data.results && data.results[0] && data.results[0].id && data.results[0].version){ + | bru.setEnvVar("${this.resourcePathName.singularize()}-id", data.results[0].id); + | bru.setEnvVar("${this.resourcePathName.singularize()}-version", data.results[0].version); + | } + | if(data.results && data.results[0] && data.results[0].key){ + | bru.setEnvVar("${this.resourcePathName.singularize()}-key", data.results[0].key); + | } + | if(data.version){ + | bru.setEnvVar("${this.resourcePathName.singularize()}-version", data.version); + | } + | if(data.id){ + | bru.setEnvVar("${this.resourcePathName.singularize()}-id", data.id); + | } + | if(data.key){ + | bru.setEnvVar("${this.resourcePathName.singularize()}-key", data.key); + | } + | ${if (param.isNotEmpty()) """ + | if(data.${param}){ + | bru.setEnvVar("${this.resourcePathName.singularize()}-${param}", data.${param}); + | } + |""".trimMargin() else ""} + |} + """.trimMargin() + } } diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModelModule.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModelModule.kt index 29b781e64..13e8da3af 100644 --- a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModelModule.kt +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModelModule.kt @@ -1,11 +1,9 @@ package io.vrap.codegen.languages.bruno.model -import io.vrap.codegen.languages.extensions.deprecated import io.vrap.rmf.codegen.di.RamlGeneratorModule import io.vrap.rmf.codegen.di.Module import io.vrap.rmf.codegen.rendering.CodeGenerator import io.vrap.rmf.codegen.rendering.FileGenerator -import io.vrap.rmf.codegen.rendering.MethodGenerator object BrunoModelModule : Module { override fun configure(generatorModule: RamlGeneratorModule) = setOf( diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModuleRenderer.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModuleRenderer.kt index 1f24e832c..5080c0213 100644 --- a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModuleRenderer.kt +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModuleRenderer.kt @@ -1,13 +1,11 @@ package io.vrap.codegen.languages.bruno.model -import com.damnhandy.uri.template.UriTemplate import com.fasterxml.jackson.core.JsonProcessingException import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.module.SimpleModule import io.vrap.codegen.languages.extensions.EObjectExtensions import io.vrap.rmf.codegen.io.TemplateFile import io.vrap.rmf.codegen.rendering.FileProducer -import io.vrap.rmf.codegen.rendering.utils.escapeAll import io.vrap.rmf.codegen.rendering.utils.keepAngleIndent import io.vrap.rmf.codegen.types.VrapTypeProvider import io.vrap.rmf.raml.model.modules.Api @@ -19,11 +17,11 @@ class BrunoModuleRenderer constructor(val api: Api, override val vrapTypeProvide override fun produceFiles(): List { return listOf( brunoJson(api), - collectionBru(api), - clientCredentialsBru(api), + collectionBru(), + clientCredentialsBru(), exampleEnvironment(api), dotEnvEnvironment(api), - dotEnvSample(api), + dotEnvSample(), gitIgnore() ) } @@ -56,7 +54,7 @@ class BrunoModuleRenderer constructor(val api: Api, override val vrapTypeProvide """.trimMargin() ) } - private fun dotEnvSample(api: Api): TemplateFile { + private fun dotEnvSample(): TemplateFile { return TemplateFile(relativePath = ".env.sample", content = """ |CTP_CLIENT_ID= @@ -102,7 +100,7 @@ class BrunoModuleRenderer constructor(val api: Api, override val vrapTypeProvide ) } - private fun collectionBru(api: Api): TemplateFile { + private fun collectionBru(): TemplateFile { return TemplateFile(relativePath = "collection.bru", content = """ |auth { @@ -116,7 +114,7 @@ class BrunoModuleRenderer constructor(val api: Api, override val vrapTypeProvide ) } - private fun clientCredentialsBru(api: Api): TemplateFile { + private fun clientCredentialsBru(): TemplateFile { return TemplateFile(relativePath = "auth/clientCredentials.bru", content = """ |meta { From eb4b0a4546338f5cd80a1d7c1bee2637682bf1a3 Mon Sep 17 00:00:00 2001 From: Martin Welgemoed Date: Thu, 23 May 2024 16:17:00 +0200 Subject: [PATCH 17/20] More unused code --- .../bruno/model/BrunoMethodRenderer.kt | 29 ------------------- .../codegen/languages/bruno/model/BrunoUrl.kt | 4 --- 2 files changed, 33 deletions(-) diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoMethodRenderer.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoMethodRenderer.kt index 8429b9c53..617b17490 100644 --- a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoMethodRenderer.kt +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoMethodRenderer.kt @@ -105,35 +105,6 @@ class BrunoMethodRenderer constructor(val api: Api, override val vrapTypeProvide return example } - - fun Resource.testScript(param: String = ""): String { - return """ - |var data = res.body; - |if(res.status == 200 || res.status == 201) { - | if(data.results && data.results[0] && data.results[0].id && data.results[0].version){ - | bru.setEnvVar("${this.resourcePathName.singularize()}-id", data.results[0].id); - | bru.setEnvVar("${this.resourcePathName.singularize()}-version", data.results[0].version); - | } - | if(data.results && data.results[0] && data.results[0].key){ - | bru.setEnvVar("${this.resourcePathName.singularize()}-key", data.results[0].key); - | } - | if(data.version){ - | bru.setEnvVar("${this.resourcePathName.singularize()}-version", data.version); - | } - | if(data.id){ - | bru.setEnvVar("${this.resourcePathName.singularize()}-id", data.id); - | } - | if(data.key){ - | bru.setEnvVar("${this.resourcePathName.singularize()}-key", data.key); - | } - | ${if (param.isNotEmpty()) """ - | if(data.${param}){ - | bru.setEnvVar("${this.resourcePathName.singularize()}-${param}", data.${param}); - | } - |""".trimMargin() else ""} - |} - """.trimMargin() - } } diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoUrl.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoUrl.kt index 2986390b9..43a1daf88 100644 --- a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoUrl.kt +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoUrl.kt @@ -43,10 +43,6 @@ class BrunoUrl (private val resource: Resource, private val method: Method, val return "${host()}${postmanUrlPath()}" } - fun path(drop: Int = 1): String { - return postmanUrlPath().split("/").drop(drop).joinToString("\",\n\"") - } - fun query(): String { return method.queryParameters.joinToString("\n") { it.queryParam() } } From 91990adc522f8fe3ebf11cdc597d917603ad962f Mon Sep 17 00:00:00 2001 From: Martin Welgemoed Date: Thu, 23 May 2024 16:23:25 +0200 Subject: [PATCH 18/20] Fixed delete with version in URL (workaround for Bruno limitation) --- .../io/vrap/codegen/languages/bruno/model/BrunoUrl.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoUrl.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoUrl.kt index 43a1daf88..219d660fe 100644 --- a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoUrl.kt +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoUrl.kt @@ -40,7 +40,11 @@ class BrunoUrl (private val resource: Resource, private val method: Method, val } fun raw(): String { - return "${host()}${postmanUrlPath()}" + val requiredParameters = method.queryParameters.filter { it.required } + val queryPart = if (requiredParameters.isNotEmpty()) { + "?${requiredParameters.joinToString("&") { "${it.name}=${it.defaultValue()}" }}" + } else "" + return "${host()}${postmanUrlPath()}${queryPart}" } fun query(): String { From 7b79162dadb7984c52558baef6adbc81eae44891 Mon Sep 17 00:00:00 2001 From: Martin Welgemoed Date: Thu, 23 May 2024 16:26:16 +0200 Subject: [PATCH 19/20] Removed unused code --- .../bruno/model/BrunoActionRenderer.kt | 3 +- .../bruno/model/BrunoMethodRenderer.kt | 2 +- .../bruno/model/BrunoModuleRenderer.kt | 31 +------------------ 3 files changed, 3 insertions(+), 33 deletions(-) diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoActionRenderer.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoActionRenderer.kt index ea619bcc4..9516811b7 100644 --- a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoActionRenderer.kt +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoActionRenderer.kt @@ -18,12 +18,11 @@ import io.vrap.rmf.raml.model.types.* import io.vrap.rmf.raml.model.util.StringCaseFormat import java.io.IOException -class BrunoActionRenderer constructor(val api: Api, override val vrapTypeProvider: VrapTypeProvider) : EObjectExtensions, FileProducer { +class BrunoActionRenderer(val api: Api, override val vrapTypeProvider: VrapTypeProvider) : EObjectExtensions, FileProducer { val offset = 1 + allResourceMethods().count() fun allResourceMethods(): List = api.allContainedResources.flatMap { it.methods } - fun allResources(): List = api.allContainedResources override fun produceFiles(): List { return updateActions(api) diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoMethodRenderer.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoMethodRenderer.kt index 617b17490..ec6ebc29b 100644 --- a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoMethodRenderer.kt +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoMethodRenderer.kt @@ -15,7 +15,7 @@ import io.vrap.rmf.raml.model.resources.Resource import io.vrap.rmf.raml.model.types.* import io.vrap.rmf.raml.model.util.StringCaseFormat -class BrunoMethodRenderer constructor(val api: Api, override val vrapTypeProvider: VrapTypeProvider) : EObjectExtensions, FileProducer { +class BrunoMethodRenderer(val api: Api, override val vrapTypeProvider: VrapTypeProvider) : EObjectExtensions, FileProducer { val offset = 1 diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModuleRenderer.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModuleRenderer.kt index 5080c0213..a1f3c05a8 100644 --- a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModuleRenderer.kt +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModuleRenderer.kt @@ -1,8 +1,5 @@ package io.vrap.codegen.languages.bruno.model -import com.fasterxml.jackson.core.JsonProcessingException -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.databind.module.SimpleModule import io.vrap.codegen.languages.extensions.EObjectExtensions import io.vrap.rmf.codegen.io.TemplateFile import io.vrap.rmf.codegen.rendering.FileProducer @@ -12,7 +9,7 @@ import io.vrap.rmf.raml.model.modules.Api import io.vrap.rmf.raml.model.resources.Resource import io.vrap.rmf.raml.model.types.* -class BrunoModuleRenderer constructor(val api: Api, override val vrapTypeProvider: VrapTypeProvider) : EObjectExtensions, FileProducer { +class BrunoModuleRenderer(val api: Api, override val vrapTypeProvider: VrapTypeProvider) : EObjectExtensions, FileProducer { override fun produceFiles(): List { return listOf( @@ -169,32 +166,6 @@ class BrunoModuleRenderer constructor(val api: Api, override val vrapTypeProvide } } -fun Instance.toJson(): String { - var example = "" - val mapper = ObjectMapper() - - val module = SimpleModule() - module.addSerializer(ObjectInstance::class.java, ObjectInstanceSerializer()) - module.addSerializer(ArrayInstance::class.java, InstanceSerializer()) - module.addSerializer(IntegerInstance::class.java, InstanceSerializer()) - module.addSerializer(BooleanInstance::class.java, InstanceSerializer()) - module.addSerializer(StringInstance::class.java, InstanceSerializer()) - module.addSerializer(NumberInstance::class.java, InstanceSerializer()) - mapper.registerModule(module) - - if (this is StringInstance) { - example = this.value - } else if (this is ObjectInstance) { - try { - example = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(this) - } catch (e: JsonProcessingException) { - } - - } - - return example -} - fun Resource.testScript(param: String = ""): String { return """ |var data = res.body; From 9d4108fdb24a4953e2528a581d919a613a9197d1 Mon Sep 17 00:00:00 2001 From: Jens Schulze Date: Thu, 23 May 2024 22:18:24 +0200 Subject: [PATCH 20/20] add docs to the collection --- .../bruno/model/BrunoModuleRenderer.kt | 47 ++++++++++++++++++- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModuleRenderer.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModuleRenderer.kt index a1f3c05a8..92ad605a3 100644 --- a/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModuleRenderer.kt +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModuleRenderer.kt @@ -23,6 +23,45 @@ class BrunoModuleRenderer(val api: Api, override val vrapTypeProvider: VrapTypeP ) } + private fun readme(api: Api): String { + return """ + # commercetools API Bruno Collection + + This collection contains examples of requests and responses for most endpoints and commands of the + ${api.title}. For every command the smallest possible payload is given. Please find optional + fields in the related official documentation. Additionally the collection provides example requests and + responses for specific tasks and more complex data models. + + ## Disclaimer + + This is not the official ${api.title} documentation. Please see [here](http://docs.commercetools.com/) + for a complete and approved documentation of the ${api.title}. + + ## How to use + + **:warning: Be aware that postman automatically synchronizes environment variables (including your API client credentials) to your workspace if logged in. + Use this collection only for development purposes and non-production projects.** + + To use this collection in Postman please perform the following steps: + + 1. Download and install the Bruno Client + 1. Fork the repository + 1. Open the collection + 1. In the Merchant Center, create a new API Client + 1. Select the environment and configure the client credentials in the variable `ctp_client_id` and `ctp_client_secret` + or create an `.env` file and put it in the collection folder. An sample file is part of the collection. + 1. Obtain an access token by sending the "Auth/Client credentials" request. Now you can use all other endpoints + + Feel free to clone and modify this collection to your needs. This collection gets automatically + updated and you can pull the latest changes. + + To automate frequent tasks the collection automatically manages commonly required values and parameters such + as resource ids, keys and versions in environment variables for you. + + Please see http://docs.commercetools.com/ for further information about the commercetools Plattform. + """.trimIndent() + } + private fun exampleEnvironment(api: Api): TemplateFile { val baseUri = when (val sdkBaseUri = api.getAnnotation("sdkBaseUri")?.value) { is StringInstance -> sdkBaseUri.value @@ -70,7 +109,7 @@ class BrunoModuleRenderer(val api: Api, override val vrapTypeProvider: VrapTypeP |vars { | authUrl: ${api.oAuth2().uri().toString().trimEnd('/')} | apiUrl: $baseUri - | projectKey: + | project-key: | ctp_client_id: {{process.env.CTP_CLIENT_ID}} | ctp_client_secret: {{process.env.CTP_CLIENT_SECRET}} |} @@ -107,7 +146,11 @@ class BrunoModuleRenderer(val api: Api, override val vrapTypeProvider: VrapTypeP |auth:bearer { | token: {{ctp_access_token}} |} - """.trimMargin() + | + |docs { + | <<${readme(api)}>> + |} + """.trimMargin().keepAngleIndent() ) }