diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9e5c8f414..50d92ed84 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,139 +6,6 @@ on: name: AutoRelease jobs: -# build-native: -# name: Build all native images -# continue-on-error: true -# strategy: -# matrix: -# include: -# - os: ubuntu-latest -# name: Linux -# artifact: rmf-codegen.linux -# - os: macos-latest -# name: Mac OS X -# artifact: rmf-codegen.darwin -# # TODO windows fails, reason not known yet -# # - os: windows-latest -# # name: Windows -# # artifact: rmf-codegen.win32 -# runs-on: ${{ matrix.os }} -# steps: -# - name: Checkout -# uses: actions/checkout@v3 -# -# - name: Setup Java -# uses: actions/setup-java@v3 -# with: -# distribution: 'temurin' -# java-version: '11' -# -# - name: Build ${{ matrix.name }} native image -# uses: gradle/gradle-command-action@v1 -# with: -# arguments: nativeImage -# build-root-directory: tools/cli-application -# gradle-executable: ./gradlew -# -# - name: Upload ${{ matrix.name }} native image -# uses: actions/upload-artifact@v1 -# with: -# name: ${{ matrix.artifact }} -# path: ./tools/cli-application/build/graal/rmf-codegen -# -# release: -# name: Build JAR and Release the artifacts -# runs-on: ubuntu-latest -# needs: build-native -# steps: -# - name: Checkout -# uses: actions/checkout@v3 -# -# - name: Setup Java -# uses: actions/setup-java@v3 -# with: -# distribution: 'temurin' -# java-version: '11' -# -# - name: Build JAR -# uses: gradle/gradle-command-action@v1 -# with: -# arguments: build -# build-root-directory: tools/cli-application -# gradle-executable: ./gradlew -# -# - name: Get current date -# id: date -# run: echo "::set-output name=date::$(date +'%Y-%m-%d_%H-%M-%S')" -# -# - name: Download native build artifacts -# uses: actions/download-artifact@v2 -# -# - name: Commit new version to README and install script -# uses: stefanzweifel/git-auto-commit-action@v4.6.0 -# with: -# commit_message: "TASK: Updating version in README" -# commit_user_name: Auto Mation -# commit_user_email: automation@commercetools.com -# commit_author: Auto Mation -# -# - name: Create GitHub Release -# id: create_github_release -# uses: actions/create-release@v1 -# env: -# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -# with: -# tag_name: ${{ github.ref }} -# release_name: ${{ github.ref }} -# draft: false -# prerelease: false -# -# - name: Upload JAR -# id: upload-release-asset -# uses: actions/upload-release-asset@v1 -# env: -# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -# with: -# upload_url: ${{ steps.create_github_release.outputs.upload_url }} -# asset_path: rmf-codegen.jar -# asset_name: rmf-codegen.jar -# asset_content_type: application/java-archive -# -# - name: Upload Linux Binary -# id: upload-linux-asset -# uses: actions/upload-release-asset@v1 -# continue-on-error: true -# env: -# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -# with: -# upload_url: ${{ steps.create_github_release.outputs.upload_url }} -# asset_path: rmf-codegen.linux/rmf-codegen -# asset_name: rmf-codegen.linux -# asset_content_type: application/octet-stream -# -# - name: Upload Mac OS Binary -# id: upload-mac-asset -# continue-on-error: true -# uses: actions/upload-release-asset@v1 -# env: -# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -# with: -# upload_url: ${{ steps.create_github_release.outputs.upload_url }} -# asset_path: rmf-codegen.darwin/rmf-codegen -# asset_name: rmf-codegen.darwin -# asset_content_type: application/octet-stream - - # - name: Upload Windows Binary - # id: upload-windows-asset - # uses: actions/upload-release-asset@v1 - # env: - # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # with: - # upload_url: ${{ steps.create_github_release.outputs.upload_url }} - # asset_path: rmf-codegen.win32/rmf-codegen - # asset_name: rmf-codegen.exe - # asset_content_type: application/octet-stream - release_maven: name: Build and release to Maven diff --git a/codegen-renderers/src/main/kotlin/io/vrap/codegen/languages/extensions/AnyTypeExtensions.kt b/codegen-renderers/src/main/kotlin/io/vrap/codegen/languages/extensions/AnyTypeExtensions.kt index 10da51e8c..df7c99c8b 100644 --- a/codegen-renderers/src/main/kotlin/io/vrap/codegen/languages/extensions/AnyTypeExtensions.kt +++ b/codegen-renderers/src/main/kotlin/io/vrap/codegen/languages/extensions/AnyTypeExtensions.kt @@ -24,3 +24,8 @@ fun AnyType.deprecated() : Boolean { val anno = this.getAnnotation("deprecated") return (anno != null && (anno.value as BooleanInstance).value) } + +fun AnyType.markDeprecated() : Boolean { + val anno = this.getAnnotation("markDeprecated") + return (anno != null && (anno.value as BooleanInstance).value) +} diff --git a/ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/ModulesValidator.kt b/ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/ModulesValidator.kt index d267df2e6..8fb4b37bd 100644 --- a/ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/ModulesValidator.kt +++ b/ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/ModulesValidator.kt @@ -2,6 +2,8 @@ package com.commercetools.rmf.validators import io.vrap.rmf.raml.model.modules.util.ModulesSwitch import io.vrap.rmf.raml.model.resources.util.ResourcesSwitch +import io.vrap.rmf.raml.model.types.AnnotationsFacet +import io.vrap.rmf.raml.model.types.ArrayInstance import io.vrap.rmf.raml.validation.AbstractRamlValidator import io.vrap.rmf.raml.validation.RamlValidator import org.eclipse.emf.common.util.Diagnostic @@ -19,7 +21,13 @@ class ModulesValidator(private val validators: List? ): Boolean { val validationResults = validators.stream() - .flatMap { validator: ModulesSwitch> -> validator.doSwitch(eObject).stream() } + .flatMap { validator: ModulesSwitch> -> + return@flatMap if (eObject is AnnotationsFacet && eObject.getAnnotation("ignoreValidators") != null && (eObject.getAnnotation("ignoreValidators").value as ArrayInstance).value.any { it.value == validator.javaClass.simpleName } ) { + emptyList().stream() + } else { + validator.doSwitch(eObject).stream() + } + } .collect(Collectors.toList()) validationResults.forEach(Consumer { diagnostic: Diagnostic? -> diagnostics.add(diagnostic) }) return validationResults.isEmpty() diff --git a/ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/PolymorphicSubtypesRule.kt b/ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/PolymorphicSubtypesRule.kt new file mode 100644 index 000000000..c932bb0ea --- /dev/null +++ b/ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/PolymorphicSubtypesRule.kt @@ -0,0 +1,39 @@ +package com.commercetools.rmf.validators + +import io.vrap.rmf.raml.model.modules.Library +import io.vrap.rmf.raml.model.types.AnyType +import io.vrap.rmf.raml.model.types.BuiltinType +import io.vrap.rmf.raml.model.types.ObjectType +import io.vrap.rmf.raml.model.types.TypeTemplate +import org.eclipse.emf.common.util.Diagnostic +import java.util.* + +//@ValidatorSet +class PolymorphicSubtypesRule(severity: RuleSeverity, options: List? = null) : TypesRule(severity, options) { + + private val exclude: List = + (options?.filter { ruleOption -> ruleOption.type.lowercase(Locale.getDefault()) == RuleOptionType.EXCLUDE.toString() }?.map { ruleOption -> ruleOption.value }?.plus("") ?: defaultExcludes) + + override fun caseAnyType(type: AnyType): List { + val validationResults: MutableList = ArrayList() + + if (exclude.contains(type.name).not() && type is ObjectType && type.subTypes.filterNot { it.isInlineType }.any() && type.discriminator == null) { + validationResults.add(create(type, "Type \"{0}\" has subtypes but no discriminator is set", type.name)) + } + return validationResults + } + + companion object : ValidatorFactory { + private val defaultExcludes by lazy { listOf("") } + + @JvmStatic + override fun create(options: List?): PolymorphicSubtypesRule { + return PolymorphicSubtypesRule(RuleSeverity.ERROR, options) + } + + @JvmStatic + override fun create(severity: RuleSeverity, options: List?): PolymorphicSubtypesRule { + return PolymorphicSubtypesRule(severity, options) + } + } +} \ No newline at end of file diff --git a/ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/QueryParameterCamelCaseRule.kt b/ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/QueryParameterCamelCaseRule.kt index 827fad782..a4ee78296 100644 --- a/ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/QueryParameterCamelCaseRule.kt +++ b/ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/QueryParameterCamelCaseRule.kt @@ -19,7 +19,7 @@ class QueryParameterCamelCaseRule(severity: RuleSeverity, options: List ): Boolean { val validationResults = validators.stream() - .flatMap { validator: ResourcesSwitch> -> validator.doSwitch(eObject).stream() } + .flatMap { validator: ResourcesSwitch> -> + return@flatMap if (eObject is AnnotationsFacet && eObject.getAnnotation("ignoreValidators") != null && (eObject.getAnnotation("ignoreValidators").value as ArrayInstance).value.any { it.value == validator.javaClass.simpleName } ) { + emptyList().stream() + } else { + validator.doSwitch(eObject).stream() + } + } .collect(Collectors.toList()) validationResults.forEach(Consumer { diagnostic: Diagnostic? -> diagnostics.add(diagnostic) }) return validationResults.isEmpty() diff --git a/ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/ResourcesValidator.kt b/ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/ResourcesValidator.kt index 3a63de260..636f656ab 100644 --- a/ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/ResourcesValidator.kt +++ b/ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/ResourcesValidator.kt @@ -1,6 +1,8 @@ package com.commercetools.rmf.validators import io.vrap.rmf.raml.model.resources.util.ResourcesSwitch +import io.vrap.rmf.raml.model.types.AnnotationsFacet +import io.vrap.rmf.raml.model.types.ArrayInstance import io.vrap.rmf.raml.validation.AbstractRamlValidator import io.vrap.rmf.raml.validation.RamlValidator import org.eclipse.emf.common.util.Diagnostic @@ -19,7 +21,13 @@ class ResourcesValidator(private val validators: List ): Boolean { val validationResults = validators.stream() - .flatMap { validator: ResourcesSwitch> -> validator.doSwitch(eObject).stream() } + .flatMap { validator: ResourcesSwitch> -> + return@flatMap if (eObject is AnnotationsFacet && eObject.getAnnotation("ignoreValidators") != null && (eObject.getAnnotation("ignoreValidators").value as ArrayInstance).value.any { it.value == validator.javaClass.simpleName } ) { + emptyList().stream() + } else { + validator.doSwitch(eObject).stream() + } + } .collect(Collectors.toList()) validationResults.forEach(Consumer { diagnostic: Diagnostic? -> diagnostics.add(diagnostic) }) return validationResults.isEmpty() diff --git a/ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/TypesValidator.kt b/ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/TypesValidator.kt index 6c077b24d..62ade5c59 100644 --- a/ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/TypesValidator.kt +++ b/ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/TypesValidator.kt @@ -1,6 +1,7 @@ package com.commercetools.rmf.validators -import io.vrap.rmf.raml.model.resources.util.ResourcesSwitch +import io.vrap.rmf.raml.model.types.AnnotationsFacet +import io.vrap.rmf.raml.model.types.ArrayInstance import io.vrap.rmf.raml.model.types.util.TypesSwitch import io.vrap.rmf.raml.validation.AbstractRamlValidator import io.vrap.rmf.raml.validation.RamlValidator @@ -20,7 +21,13 @@ class TypesValidator(private val validators: List>> context: Map ): Boolean { val validationResults = validators.stream() - .flatMap { validator: TypesSwitch> -> validator.doSwitch(eObject).stream() } + .flatMap { validator: TypesSwitch> -> + return@flatMap if (eObject is AnnotationsFacet && eObject.getAnnotation("ignoreValidators") != null && (eObject.getAnnotation("ignoreValidators").value as ArrayInstance).value.any { it.value == validator.javaClass.simpleName } ) { + emptyList().stream() + } else { + validator.doSwitch(eObject).stream() + } + } .collect(Collectors.toList()) validationResults.forEach(Consumer { diagnostic: Diagnostic? -> diagnostics.add(diagnostic) }) return validationResults.isEmpty() diff --git a/ctp-validators/src/test/groovy/com/commercetools/rmf/validators/ValidatorRulesTest.groovy b/ctp-validators/src/test/groovy/com/commercetools/rmf/validators/ValidatorRulesTest.groovy index 2394572b1..b271ce0d3 100644 --- a/ctp-validators/src/test/groovy/com/commercetools/rmf/validators/ValidatorRulesTest.groovy +++ b/ctp-validators/src/test/groovy/com/commercetools/rmf/validators/ValidatorRulesTest.groovy @@ -397,4 +397,14 @@ class ValidatorRulesTest extends Specification implements ValidatorFixtures { result.validationResults[0].message == "Property \"/invalid/\" must define object type for placeholder annotation" } + def "subtypes should be discriminated"() { + when: + def validators = Arrays.asList(new TypesValidator(Arrays.asList(PolymorphicSubtypesRule.create(emptyList())))) + def uri = uriFromClasspath("/polymorphic-subtype-rule.raml") + def result = new RamlModelBuilder(validators).buildApi(uri) + then: + result.validationResults.size == 1 + result.validationResults[0].message == "Type \"InvalidBaz\" has subtypes but no discriminator is set" + } + } diff --git a/ctp-validators/src/test/resources/polymorphic-subtype-rule.raml b/ctp-validators/src/test/resources/polymorphic-subtype-rule.raml new file mode 100644 index 000000000..1172d0316 --- /dev/null +++ b/ctp-validators/src/test/resources/polymorphic-subtype-rule.raml @@ -0,0 +1,38 @@ +#%RAML 1.0 +title: discriminator subtype rule + +types: + Foo: + type: object + discriminator: type + properties: + type: string + SubFoo: + discriminatorValue: sub + type: Foo + Bar: + type: object + description: InvalidBar + properties: + name: string + FooBar: + description: FooBar + type: object + properties: + bar: Bar + FooBar2: + type: object + properties: + bar: + description: Bar + type: Bar + InvalidBaz: + type: object + description: InvalidBar + properties: + name: string + SubBaz: + description: SubBaz + type: InvalidBaz + SubBaz2: + type: InvalidBaz diff --git a/dependencies.gradle b/dependencies.gradle index cfcee572a..f4d24acec 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -11,7 +11,7 @@ ext { javaxValidation: '2.0.1.Final', kotlin: kotlinVersion, picocli: '4.6.1', - rmf: '0.2.0-20240119124459', + rmf: '0.2.0-20240722205528', rxjava: '3.1.2', slf4j: '1.7.25', spock: '2.2-groovy-4.0', 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/BrunoActionRenderer.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoActionRenderer.kt new file mode 100644 index 000000000..9516811b7 --- /dev/null +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoActionRenderer.kt @@ -0,0 +1,177 @@ +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 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.utils.keepAngleIndent +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.util.StringCaseFormat +import java.io.IOException + +class BrunoActionRenderer(val api: Api, override val vrapTypeProvider: VrapTypeProvider) : EObjectExtensions, FileProducer { + + val offset = 1 + allResourceMethods().count() + + fun allResourceMethods(): List = api.allContainedResources.flatMap { it.methods } + + 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 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) + 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" + + return TemplateFile( + relativePath = relativePath, + content = content + ) + } + + 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) + } + + 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 + } +} + + 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/BrunoMethodRenderer.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoMethodRenderer.kt new file mode 100644 index 000000000..ec6ebc29b --- /dev/null +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoMethodRenderer.kt @@ -0,0 +1,110 @@ +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.* +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.utils.keepAngleIndent +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.util.StringCaseFormat + +class BrunoMethodRenderer(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() + } + + private fun methods(): List { + return allResourceMethods().mapIndexed { index, method -> render(index, method) } + } + + fun render(index: Int, type: Method): TemplateFile { + + val content = renderStr(index, type).trimMargin().keepAngleIndent() + + val relativePath = methodResourcePath(type) + "/" + type.toRequestName() + ".bru" + + return TemplateFile( + relativePath = relativePath, + content = content + ) + } + + 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) + }} + val name = method.displayName?.value ?: "${method.methodName} ${method.resource().toResourceName()}" + return BrunoRequestRenderer.renderRequest(name, method, url, method.getExample(), index + offset) + } + + 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()) + + 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 + } +} + + 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..13e8da3af --- /dev/null +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModelModule.kt @@ -0,0 +1,18 @@ +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()), + BrunoMethodRenderer(generatorModule.provideRamlModel(), generatorModule.vrapTypeProvider()), + BrunoActionRenderer(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..1f8e157f7 --- /dev/null +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoModuleRenderer.kt @@ -0,0 +1,246 @@ +package io.vrap.codegen.languages.bruno.model + +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.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(val api: Api, override val vrapTypeProvider: VrapTypeProvider) : EObjectExtensions, FileProducer { + + override fun produceFiles(): List { + return listOf( + brunoJson(api), + collectionBru(), + clientCredentialsBru(), + exampleEnvironment(api), + dotEnvEnvironment(), + dotEnvSample(api), + gitIgnore() + ) + } + + 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 Bruno 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 + else -> api.baseUri.template + }.trimEnd('/') + + return TemplateFile(relativePath = "environments/Example.bru", + content = """ + |vars { + | authUrl: https://${api.oAuth2().uri().host} + | apiUrl: $baseUri + | project-key: + |} + |vars:secret [ + | ctp_client_id, + | ctp_client_secret, + | ctp_access_token + |] + """.trimMargin() + ) + } + + private fun gitIgnore(): TemplateFile { + return TemplateFile(relativePath = ".gitignore", + content = """ + |.env + """.trimMargin() + ) + } + private fun dotEnvSample(api: Api): TemplateFile { + val baseUri = when (val sdkBaseUri = api.getAnnotation("sdkBaseUri")?.value) { + is StringInstance -> sdkBaseUri.value + else -> api.baseUri.template + }.trimEnd('/') + return TemplateFile(relativePath = ".env.sample", + content = """ + |CTP_CLIENT_ID= + |CTP_CLIENT_SECRET= + |CTP_API_URL=$baseUri + |CTP_AUTH_URL=https://${api.oAuth2().uri().host} + """.trimMargin() + ) + } + + private fun dotEnvEnvironment(): TemplateFile { + return TemplateFile(relativePath = "environments/DotEnv.bru", + content = """ + |vars { + | authUrl: {{process.env.CTP_AUTH_URL}} + | apiUrl: {{process.env.CTP_API_URL}} + | project-key: + | ctp_client_id: {{process.env.CTP_CLIENT_ID}} + | ctp_client_secret: {{process.env.CTP_CLIENT_SECRET}} + |} + |vars:secret [ + | ctp_access_token + |] + """.trimMargin().keepAngleIndent() + ) + } + + private fun brunoJson(api: Api): TemplateFile { + return TemplateFile(relativePath = "bruno.json", + content = """ + |{ + | "version": "1", + | "name": "${api.title}", + | "type": "collection", + | "ignore": [ + | "node_modules", + | ".git" + | ] + |} + """.trimMargin() + ) + } + + private fun collectionBru(): TemplateFile { + return TemplateFile(relativePath = "collection.bru", + content = """ + |headers { + | User-Agent: bruno/0.1.0 + |} + | + |auth { + | mode: bearer + |} + | + |auth:bearer { + | token: {{ctp_access_token}} + |} + | + |docs { + | <<${readme(api)}>> + |} + """.trimMargin().keepAngleIndent() + ) + } + + private fun clientCredentialsBru(): TemplateFile { + return TemplateFile(relativePath = "auth/clientCredentials.bru", + content = """ + |meta { + | name: Client Credentials + | type: http + | seq: 1 + |} + | + |post { + | url: {{authUrl}}/oauth/token + | body: formUrlEncoded + | auth: basic + |} + | + |body:form-urlencoded { + | grant_type: client_credentials + |} + | + |auth:basic { + | username: {{ctp_client_id}} + | password: {{ctp_client_secret}} + |} + | + |script:post-response { + | if(res.status == 200) { + | 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("project-key", scopeParts[1]); + | parts = parts.filter(scope => scope.length >= 3) + | if (parts.length > 0) { + | scopeParts = parts[0]; + | bru.setEnvVar("store-key", scopeParts[2]); + | } + | } + | } + | } + |} + | + |assert { + | res.status: eq 200 + |} + """.trimMargin() + ) + } +} + +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/BrunoRequestRenderer.kt b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoRequestRenderer.kt new file mode 100644 index 000000000..d7984e49a --- /dev/null +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoRequestRenderer.kt @@ -0,0 +1,67 @@ +package io.vrap.codegen.languages.bruno.model + +import com.fasterxml.jackson.databind.ObjectMapper +import com.google.common.net.MediaType +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 mediaType = when(method.bodies.getOrNull(0)?.contentType) { + "application/graphql" -> "graphql" + "application/json" -> "json" + else -> "none" + } + val metaType = when(mediaType) { + "graphql" -> "graphql" + else -> "http" + } + val mapper = ObjectMapper(); + val query = if (mediaType == "graphql") { + mapper.readTree(body).get("query")?.asText() + } else "" + val bodyStr = if (mediaType == "graphql") { + """ + |body:graphql { + | <<${query?.trim()}>> + |} + |body:graphql:vars { + | <<${mapper.readTree(body).get("variables")?.toPrettyString()}>> + |} + """.trimMargin().keepAngleIndent() + } else if (mediaType == "json" && body != null) """ + |body:json { + | <<${body}>> + |} + """.trimMargin().keepAngleIndent() + else "" + return """ + |meta { + | name: $name + | type: $metaType + | seq: $index + |} + | + |${method.methodName} { + | url: ${url.raw()} + | body: $mediaType + | 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 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..219d660fe --- /dev/null +++ b/languages/bruno/src/main/kotlin/io/vrap/codegen/languages/bruno/model/BrunoUrl.kt @@ -0,0 +1,72 @@ +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 "{{apiUrl}}" + } + + 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 { + 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 { + return method.queryParameters.joinToString("\n") { it.queryParam() } + } + + private fun QueryParameter.queryParam() : String { + val disabled = if (this.required) "" else "~" + return "${disabled}${this.name}: ${this.defaultValue()}" + } + + 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/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..0696f40b9 --- /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 generateBrunoModels() { + 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/languages/csharp/src/main/kotlin/io/vrap/codegen/languages/csharp/extensions/CsharpObjectTypeExtensions.kt b/languages/csharp/src/main/kotlin/io/vrap/codegen/languages/csharp/extensions/CsharpObjectTypeExtensions.kt index ab8f04195..23e2c4ae5 100644 --- a/languages/csharp/src/main/kotlin/io/vrap/codegen/languages/csharp/extensions/CsharpObjectTypeExtensions.kt +++ b/languages/csharp/src/main/kotlin/io/vrap/codegen/languages/csharp/extensions/CsharpObjectTypeExtensions.kt @@ -2,13 +2,14 @@ package io.vrap.codegen.languages.csharp.extensions import io.vrap.codegen.languages.extensions.* import io.vrap.rmf.codegen.firstUpperCase -import io.vrap.rmf.codegen.types.VrapArrayType -import io.vrap.rmf.codegen.types.VrapEnumType -import io.vrap.rmf.codegen.types.VrapObjectType -import io.vrap.rmf.codegen.types.VrapType +import io.vrap.rmf.codegen.types.* +import io.vrap.rmf.raml.model.types.ArrayType import io.vrap.rmf.raml.model.types.BooleanInstance +import io.vrap.rmf.raml.model.types.DateOnlyType +import io.vrap.rmf.raml.model.types.DateTimeType import io.vrap.rmf.raml.model.types.ObjectType import io.vrap.rmf.raml.model.types.Property +import io.vrap.codegen.languages.extensions.EObjectExtensions const val ANNOTATION_ABSTRACT = "abstract" @@ -32,27 +33,52 @@ interface CsharpObjectTypeExtensions : ExtensionsBase { return usingsList } - fun ObjectType.usings() : String { + fun ObjectType.usings(provider: VrapTypeProvider, isInterface: Boolean = false, isDictionary: Boolean = false) : String { var usingsAsList = this.getUsings() val vrapType = vrapTypeProvider.doSwitch(this) as VrapObjectType var packageName = vrapType.`package` - usingsAsList = usingsAsList.dropLastWhile { it== packageName } + usingsAsList = usingsAsList.filterNot { it == vrapType.csharpPackage() } var usings= usingsAsList.map { "using $it;" }.joinToString(separator = "\n") - var commonUsings = this.getCommonUsings() + var commonUsings = this.getCommonUsings(provider, isInterface, isDictionary) var allusings = if(usings.isNotEmpty()) usings +"\n"+ commonUsings else usings+ commonUsings return allusings } - fun ObjectType.getCommonUsings() : String { - return """using System; - |using System.Collections.Generic; - |using System.Linq; - |using System.Text.Json.Serialization; - |using commercetools.Base.CustomAttributes; - |using commercetools.Base.Models; - |""" +// fun ObjectType.getCommonUsings() : String { +// return """using System; +// |using System.Collections.Generic; +// |using System.Linq; +// |using System.Text.Json.Serialization; +// |using commercetools.Base.CustomAttributes; +// |using commercetools.Base.Models; +// |""" +// } + + fun ObjectType.getCommonUsings(provider: VrapTypeProvider, isInterface: Boolean = false, isDictionary: Boolean = false) : String { + + var usings = listOf() + val props = this.allProperties.map { provider.doSwitch(it.type) } + if (props.any { it.simpleName() == "Object" || it.simpleName() == "DateTime" || it.simpleName() == "TimeSpan" } || this.allProperties.any { it.markDeprecated() } || this.markDeprecated()) { + usings = usings.plus("System") + } + if (isDictionary || props.any { it is VrapArrayType }) { + usings = usings.plus("System.Collections.Generic") + .plus("System.Linq") + } + if (props.any { it is VrapArrayType && (it.itemType.simpleName() == "Object" || it.itemType.simpleName() == "DateTime" || it.itemType.simpleName() == "TimeSpan") }) { + usings = usings.plus("System") + } + if (props.any { it.simpleName() == "Date" || (it is VrapArrayType && (it.itemType.simpleName() == "Date")) }) { + usings = usings.plus("commercetools.Base.Models") + } + if (isInterface || this.discriminator.isNullOrEmpty().not() || (this.isInlineType && (this.type as ObjectType).discriminator.isNullOrEmpty().not())) { + usings = usings.plus("commercetools.Base.CustomAttributes") + .plus("System") + } + + return usings.distinct().joinToString("\n") { "using $it;" } } public fun ObjectType.objectClassName(): String { diff --git a/languages/csharp/src/main/kotlin/io/vrap/codegen/languages/csharp/extensions/CsharpVrapExtensions.kt b/languages/csharp/src/main/kotlin/io/vrap/codegen/languages/csharp/extensions/CsharpVrapExtensions.kt index 0cccb88bf..d15f27bd5 100644 --- a/languages/csharp/src/main/kotlin/io/vrap/codegen/languages/csharp/extensions/CsharpVrapExtensions.kt +++ b/languages/csharp/src/main/kotlin/io/vrap/codegen/languages/csharp/extensions/CsharpVrapExtensions.kt @@ -180,3 +180,12 @@ fun Property.deprecated() : Boolean { return (typeAnno != null && (typeAnno.value as BooleanInstance).value) } +fun Property.markDeprecated() : Boolean { + val anno = this.getAnnotation("markDeprecated") + if (anno != null) { + return (anno.value as BooleanInstance).value + } + val typeAnno = this.type.getAnnotation("markDeprecated") + return (typeAnno != null && (typeAnno.value as BooleanInstance).value) +} + diff --git a/languages/csharp/src/main/kotlin/io/vrap/codegen/languages/csharp/model/CsharpModelInterfaceRenderer.kt b/languages/csharp/src/main/kotlin/io/vrap/codegen/languages/csharp/model/CsharpModelInterfaceRenderer.kt index 545899357..5cc76d29c 100644 --- a/languages/csharp/src/main/kotlin/io/vrap/codegen/languages/csharp/model/CsharpModelInterfaceRenderer.kt +++ b/languages/csharp/src/main/kotlin/io/vrap/codegen/languages/csharp/model/CsharpModelInterfaceRenderer.kt @@ -10,6 +10,7 @@ import io.vrap.rmf.codegen.di.BasePackageName import io.vrap.rmf.codegen.io.TemplateFile import io.vrap.rmf.codegen.rendering.ObjectTypeRenderer 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.VrapArrayType import io.vrap.rmf.codegen.types.VrapObjectType @@ -36,20 +37,20 @@ class CsharpModelInterfaceRenderer constructor(override val vrapTypeProvider: Vr ).filterNotNull() var content : String = """ - |${type.usings()} + |${type.usings(vrapTypeProvider, true)} |// ReSharper disable CheckNamespace |namespace ${vrapType.csharpPackage()} |{ | <${type.DeserializationAttributes()}> - | public partial interface I${vrapType.simpleClassName} ${if (extends.isNotEmpty()) { ": ${extends.joinToString(separator = ", ")}" } else ""} + | public partial interface I${vrapType.simpleClassName}${if (extends.isNotEmpty()) { " : ${extends.joinToString(separator = ", ")}" } else ""} | { | <${type.toProperties()}> - | + | | <${type.subtypeFactories()}> | } |} | - """.trimMargin().keepIndentation() + """.trimMargin().keepIndentation().split("\n").joinToString(separator = "\n") { it.trimEnd() } if(type.isADictionaryType()) @@ -65,12 +66,12 @@ class CsharpModelInterfaceRenderer constructor(override val vrapTypeProvider: Vr ) } - private fun ObjectType.toProperties() : String = this.properties + private fun ObjectType.toProperties(indent: String = "") : String = this.properties .filterNot { it.deprecated() } .filterNot { property -> property.isPatternProperty() } .map { it.toCsharpProperty(this) }.joinToString(separator = "\n\n") - private fun Property.toCsharpProperty(objectType: ObjectType): String { + private fun Property.toCsharpProperty(objectType: ObjectType, indent: String = ""): String { val propName = this.name.firstUpperCase() val typeName = this.type.toVrapType().simpleName() val overrideProp = this.shouldOverrideThisProperty(objectType) @@ -80,9 +81,9 @@ class CsharpModelInterfaceRenderer constructor(override val vrapTypeProvider: Vr val deprecationAttr = if(this.deprecationAnnotation() == "") "" else this.deprecationAnnotation()+"\n"; return """ - |${deprecationAttr}${newKeyword}${typeName}$nullableChar $propName { get; set;}${if (this.type.toVrapType() is VrapArrayType) """ - |${deprecationAttr}${newKeyword}IEnumerable\<${(this.type.toVrapType() as VrapArrayType).itemType.simpleName()}\>$nullableChar ${propName}Enumerable { set =\> $propName = value$nullableChar.ToList(); } - |""" else ""} + |${deprecationAttr}${newKeyword}${typeName}$nullableChar $propName { get; set; }${if (this.type.toVrapType() is VrapArrayType) """ + | + |${deprecationAttr}${newKeyword}IEnumerable\<${(this.type.toVrapType() as VrapArrayType).itemType.simpleName()}\>$nullableChar ${propName}Enumerable { set =\> $propName = value$nullableChar.ToList(); }""" else ""} """.trimMargin() } @@ -114,7 +115,7 @@ class CsharpModelInterfaceRenderer constructor(override val vrapTypeProvider: Vr val property = this.properties[0] return """ - |${this.usings()} + |${this.usings(vrapTypeProvider, true, true)} | |namespace ${vrapType.csharpPackage()} |{ diff --git a/languages/csharp/src/main/kotlin/io/vrap/codegen/languages/csharp/model/CsharpObjectTypeRenderer.kt b/languages/csharp/src/main/kotlin/io/vrap/codegen/languages/csharp/model/CsharpObjectTypeRenderer.kt index 4ca911f4b..928692102 100644 --- a/languages/csharp/src/main/kotlin/io/vrap/codegen/languages/csharp/model/CsharpObjectTypeRenderer.kt +++ b/languages/csharp/src/main/kotlin/io/vrap/codegen/languages/csharp/model/CsharpObjectTypeRenderer.kt @@ -23,7 +23,7 @@ class CsharpObjectTypeRenderer constructor(override val vrapTypeProvider: VrapTy val vrapType = vrapTypeProvider.doSwitch(type) as VrapObjectType var content : String = """ - |${type.usings()} + |${type.usings(vrapTypeProvider)} | |namespace ${vrapType.csharpPackage()} |{ @@ -35,7 +35,7 @@ class CsharpObjectTypeRenderer constructor(override val vrapTypeProvider: VrapTy | } |} | - """.trimMargin().keepIndentation() + """.trimMargin().keepIndentation().split("\n").joinToString(separator = "\n") { it.trimEnd() } if(type.isADictionaryType()) { @@ -56,7 +56,7 @@ class CsharpObjectTypeRenderer constructor(override val vrapTypeProvider: VrapTy var property = this.properties[0] return """ - |${this.usings()} + |${this.usings(vrapTypeProvider, false, true)} | |// ReSharper disable CheckNamespace |namespace ${vrapType.csharpPackage()} @@ -83,9 +83,9 @@ class CsharpObjectTypeRenderer constructor(override val vrapTypeProvider: VrapTy val deprecationAttr = if(this.deprecationAnnotation() == "") "" else this.deprecationAnnotation()+"\n"; return """ - |${deprecationAttr}public ${typeName}$nullableChar $propName { get; set;}${if (this.type.toVrapType() is VrapArrayType) """ - |${deprecationAttr}public IEnumerable\<${(this.type.toVrapType() as VrapArrayType).itemType.simpleName()}\>$nullableChar ${propName}Enumerable { set =\> $propName = value$nullableChar.ToList(); } - |""" else ""} + |${deprecationAttr}public ${typeName}$nullableChar $propName { get; set; }${if (this.type.toVrapType() is VrapArrayType) """ + | + |${deprecationAttr}public IEnumerable\<${(this.type.toVrapType() as VrapArrayType).itemType.simpleName()}\>$nullableChar ${propName}Enumerable { set =\> $propName = value$nullableChar.ToList(); }""" else ""} """.trimMargin() } @@ -103,8 +103,8 @@ class CsharpObjectTypeRenderer constructor(override val vrapTypeProvider: VrapTy val isEmptyConstructor = this.getConstructorContentForDiscriminator() == ""; return if(!isEmptyConstructor) """public ${className}() - |{ - | ${this.getConstructorContentForDiscriminator()} + |{ + | ${this.getConstructorContentForDiscriminator()} |}""" else "" diff --git a/languages/csharp/src/main/kotlin/io/vrap/codegen/languages/csharp/model/CsharpStringTypeRenderer.kt b/languages/csharp/src/main/kotlin/io/vrap/codegen/languages/csharp/model/CsharpStringTypeRenderer.kt index c4a35ad5e..c4d19db9d 100644 --- a/languages/csharp/src/main/kotlin/io/vrap/codegen/languages/csharp/model/CsharpStringTypeRenderer.kt +++ b/languages/csharp/src/main/kotlin/io/vrap/codegen/languages/csharp/model/CsharpStringTypeRenderer.kt @@ -40,7 +40,7 @@ class CsharpStringTypeRenderer constructor(override val vrapTypeProvider: VrapTy | { | return JsonName; | } - | + | | IEnumerator IEnumerable.GetEnumerator() | { | return GetEnumerator(); @@ -55,21 +55,21 @@ class CsharpStringTypeRenderer constructor(override val vrapTypeProvider: VrapTy | [EnumInterfaceCreator(typeof(I${vrapType.simpleClassName}), "FindEnum")] | public interface I${vrapType.simpleClassName} : IJsonName, IEnumerable\ | { - | <${type.enumStaticFields("${vrapType.simpleClassName}")}> + | <${type.enumStaticFields("${vrapType.simpleClassName}")}> + | + | ${vrapType.simpleClassName}? Value { get; } | - | ${vrapType.simpleClassName}? Value { get; } - | - | static I${vrapType.simpleClassName}[] Values() - | { - | return new[] - | { + | static I${vrapType.simpleClassName}[] Values() + | { + | return new[] + | { | <${type.enumFieldsAsNames()}> | }; - | } - | static I${vrapType.simpleClassName} FindEnum(string value) - | { - | return Values().FirstOrDefault(origin =\> origin.JsonName == value) ?? new ${vrapType.simpleClassName}Wrapper() {JsonName = value}; - | } + | } + | static I${vrapType.simpleClassName} FindEnum(string value) + | { + | return Values().FirstOrDefault(origin =\> origin.JsonName == value) ?? new ${vrapType.simpleClassName}Wrapper() { JsonName = value }; + | } | } |} """.trimMargin().keepIndentation() @@ -95,7 +95,7 @@ class CsharpStringTypeRenderer constructor(override val vrapTypeProvider: VrapTy ?.map { """ |public static I${enumName} ${it.value.enumValueName()} = new ${enumName}Wrapper - | {Value = ${enumName}.${it.value.enumValueName()}, JsonName = "${it.value}"}; + |{ Value = ${enumName}.${it.value.enumValueName()}, JsonName = "${it.value}" }; """.trimMargin() } ?.joinToString(separator = "\n\n", postfix = "") @@ -103,10 +103,10 @@ class CsharpStringTypeRenderer constructor(override val vrapTypeProvider: VrapTy fun StringType.enumFieldsAsNames() = enumValues() ?.map { """ - |${it.value.enumValueName()} + |${it.value.enumValueName()} """.trimMargin() } - ?.joinToString(separator = ",\n", postfix = "") + ?.joinToString(separator = " ,\n", postfix = "") fun StringType.enumValues() = enum?.filter { it is StringInstance } diff --git a/languages/csharp/src/main/kotlin/io/vrap/codegen/languages/csharp/predicates/CsharpQueryPredicateRenderer.kt b/languages/csharp/src/main/kotlin/io/vrap/codegen/languages/csharp/predicates/CsharpQueryPredicateRenderer.kt index c14b3db61..cd4c88d24 100644 --- a/languages/csharp/src/main/kotlin/io/vrap/codegen/languages/csharp/predicates/CsharpQueryPredicateRenderer.kt +++ b/languages/csharp/src/main/kotlin/io/vrap/codegen/languages/csharp/predicates/CsharpQueryPredicateRenderer.kt @@ -56,7 +56,7 @@ class CsharpQueryPredicateRenderer constructor(val basePackage: String, override | } | | <${type.allProperties.filterNot { it.deprecated() }.filterNot { it.isPatternProperty() }.joinToString("\n") { it.toBuilderDsl(type) }}> - | + | | <${type.namedSubTypes().filterIsInstance().joinToString("\n") { it.toBuilderDsl("As${it.subtypeName()}", vrapType) }}> | } |} diff --git a/languages/javalang/builder-renderer/java-builder-client/src/main/kotlin/io/vrap/codegen/languages/javalang/client/builder/model/JavaModelInterfaceRenderer.kt b/languages/javalang/builder-renderer/java-builder-client/src/main/kotlin/io/vrap/codegen/languages/javalang/client/builder/model/JavaModelInterfaceRenderer.kt index b7595932d..dfdb6f2c1 100644 --- a/languages/javalang/builder-renderer/java-builder-client/src/main/kotlin/io/vrap/codegen/languages/javalang/client/builder/model/JavaModelInterfaceRenderer.kt +++ b/languages/javalang/builder-renderer/java-builder-client/src/main/kotlin/io/vrap/codegen/languages/javalang/client/builder/model/JavaModelInterfaceRenderer.kt @@ -52,9 +52,9 @@ class JavaModelInterfaceRenderer constructor(override val vrapTypeProvider: Vrap |import com.fasterxml.jackson.databind.annotation.*; |import io.vrap.rmf.base.client.utils.Generated; |import io.vrap.rmf.base.client.Accessor; - |import javax.validation.Valid; + |import jakarta.validation.Valid; |import javax.annotation.Nullable; - |import javax.validation.constraints.NotNull; + |import jakarta.validation.constraints.NotNull; |import java.util.*; |import java.time.*; |import java.util.function.Function; diff --git a/languages/javalang/builder-renderer/java-builder-client/src/main/kotlin/io/vrap/codegen/languages/javalang/client/builder/predicates/JavaExpansionPredicateRenderer.kt b/languages/javalang/builder-renderer/java-builder-client/src/main/kotlin/io/vrap/codegen/languages/javalang/client/builder/predicates/JavaExpansionPredicateRenderer.kt new file mode 100644 index 000000000..1f27ca013 --- /dev/null +++ b/languages/javalang/builder-renderer/java-builder-client/src/main/kotlin/io/vrap/codegen/languages/javalang/client/builder/predicates/JavaExpansionPredicateRenderer.kt @@ -0,0 +1,176 @@ +package io.vrap.codegen.languages.javalang.client.builder.predicates; + +import com.google.common.collect.Lists +import io.vrap.codegen.languages.extensions.hasSubtypes +import io.vrap.codegen.languages.extensions.isPatternProperty +import io.vrap.codegen.languages.extensions.namedSubTypes +import io.vrap.codegen.languages.java.base.extensions.* +import io.vrap.rmf.codegen.firstLowerCase +import io.vrap.rmf.codegen.firstUpperCase +import io.vrap.rmf.codegen.io.TemplateFile +import io.vrap.rmf.codegen.rendering.ObjectTypeRenderer +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.* +import io.vrap.rmf.raml.model.types.* +import io.vrap.rmf.raml.model.types.Annotation +import io.vrap.rmf.raml.model.util.StringCaseFormat +import java.time.LocalDate +import java.time.LocalTime +import java.time.ZonedDateTime +import javax.lang.model.SourceVersion + +class JavaExpansionPredicateRenderer constructor(val basePackageName: String, override val vrapTypeProvider: VrapTypeProvider) : JavaObjectTypeExtensions, + JavaEObjectTypeExtensions, ObjectTypeRenderer { + + override fun render(type: ObjectType): TemplateFile { + val vrapType = vrapTypeProvider.doSwitch(type).toJavaVType() as VrapObjectType + + val implements = listOf("ExpansionDsl") + .plus( + when (val ex = type.getAnnotation("java-expansion-implements") ) { + is Annotation -> { + (ex.value as StringInstance).value.escapeAll() + } + else -> null + } + ) + .filterNotNull() + + val content = """ + |package ${vrapType.`package`.predicatePackage()}; + | + |import com.commercetools.api.predicates.expansion.ExpansionDsl; + | + |import static com.commercetools.api.predicates.expansion.ExpansionUtil.appendOne; + | + |import java.util.Collections; + |import java.util.List; + | + |${if (type.markDeprecated()) """ + |@Deprecated""" else ""} + |public class ${vrapType.builderDslName()} ${if (implements.isNotEmpty()) { "implements ${implements.joinToString(separator = ", ")}" } else ""} { + | + | private final List path; + | + | private ${vrapType.builderDslName()}(final List path) { + | this.path = path; + | } + | + | public static ${vrapType.builderDslName()} of() { + | return new ${vrapType.builderDslName()}(Collections.emptyList()); + | } + | + | public static ${vrapType.builderDslName()} of(final List path) { + | return new ${vrapType.builderDslName()}(path); + | } + | + | @Override + | public List getPath() { + | return path; + | } + | + | <<${type.allProperties.asSequence() + .filterNot { it.deprecated() } + .filterNot { it.type.isScalar() } + .filter { it.type.isExpandable() || (it.type is ArrayType && (it.type as ArrayType).items.isExpandable()) } + .joinToString("\n") { it.toBuilderDsl(type) }}>> + |} + """.trimMargin().keepAngleIndent() + + return TemplateFile( + relativePath = "${vrapType.`package`.predicatePackage()}.${vrapType.builderDslName()}".replace(".", "/") + ".java", + content = content + ) + } + + private fun String.predicatePackage() : String { + return this.replace(".models.", ".predicates.expansion.") + } + + private fun Property.toBuilderDsl(type: ObjectType) : String { + val vrapType = type.toVrapType() + val propType = this.type.toVrapType() + + return propType.toBuilderDsl(this.name, this); + } + + private fun VrapType.toBuilderDsl(propertyName: String, property: Property) : String { + var methodName = propertyName + if(SourceVersion.isKeyword(methodName)) { + methodName = "_$methodName" + } + + if (property.isPatternProperty()) { + if (this.fullClassName() == "java.lang.Object") { + return """ + |public ${basePackageName.toJavaPackage()}.predicates.expansion.ObjectExpansionDsl withName(final String name) { + | return ${basePackageName.toJavaPackage()}.predicates.expansion.ObjectExpansionDsl.of(appendOne(path, name)); + |} + """.trimMargin() + } + return """ + |public ${(this as VrapObjectType).builderDslName(true)} withName(final String name) { + | return ${this.builderDslName(true)}.of(appendOne(path, name)); + |} + """.trimMargin() + } + if (this is VrapArrayType) { + return """ + |public ${this.builderDslName(true)} $methodName() { + | return ${this.builderDslName(true)}.of(appendOne(path, "$propertyName[*]")); + |} + |public ${this.builderDslName(true)} $methodName(long index) { + | return ${this.builderDslName(true)}.of(appendOne(path, "$propertyName[" + index + "]")); + |} + """.trimMargin() + } + if (this.fullClassName() == "java.lang.Object") { + return """ + |public ${basePackageName.toJavaPackage()}.predicates.expansion.ObjectExpansionDsl $methodName() { + | return ${basePackageName.toJavaPackage()}.predicates.expansion.ObjectExpansionDsl.of(appendOne(path, "$propertyName")); + |} + """.trimMargin() + } + + return """ + |public ${(this as VrapObjectType).builderDslName(true)} $methodName() { + | return ${this.builderDslName(true)}.of(appendOne(path, "$propertyName")); + |} + """.trimMargin() + } + + private fun VrapArrayType.builderDslName(fqcn: Boolean = false) : String { + return "${if (fqcn) this.itemType.fullClassName().predicatePackage() else this.itemType.simpleName()}ExpansionBuilderDsl" + } + + private fun VrapObjectType.builderDslName(fqcn: Boolean = false) : String { + return "${if (fqcn) this.fullClassName().predicatePackage() else this.simpleName()}ExpansionBuilderDsl" + } + + private fun ObjectType.markDeprecated() : Boolean { + val anno = this.getAnnotation("markDeprecated") + return (anno != null && (anno.value as BooleanInstance).value) + } + + private fun AnyType.isAnyType(): Boolean { + return this.isScalar().not() && this !is ObjectType && this !is ArrayType && this !is UnionType && this !is IntersectionType && this !is NilType + } + + private fun AnyType.isScalar(): Boolean { + return when (this) { + is StringType -> true + is IntegerType -> true + is NumberType -> true + is BooleanType -> true + is DateTimeType -> true + is DateOnlyType -> true + is DateTimeOnlyType -> true + is TimeOnlyType -> true + is ArrayType -> this.items == null || this.items.isScalar() + else -> false + } + } + +} diff --git a/languages/javalang/builder-renderer/java-builder-client/src/main/kotlin/io/vrap/codegen/languages/javalang/client/builder/predicates/JavaExpansionProducer.kt b/languages/javalang/builder-renderer/java-builder-client/src/main/kotlin/io/vrap/codegen/languages/javalang/client/builder/predicates/JavaExpansionProducer.kt new file mode 100644 index 000000000..5d3506fb7 --- /dev/null +++ b/languages/javalang/builder-renderer/java-builder-client/src/main/kotlin/io/vrap/codegen/languages/javalang/client/builder/predicates/JavaExpansionProducer.kt @@ -0,0 +1,67 @@ +package io.vrap.codegen.languages.javalang.client.builder.predicates + +import io.vrap.codegen.languages.java.base.JavaSubTemplates +import io.vrap.codegen.languages.java.base.extensions.toJavaPackage +import io.vrap.rmf.codegen.io.TemplateFile +import io.vrap.rmf.codegen.rendering.FileProducer +import io.vrap.rmf.codegen.rendering.utils.keepAngleIndent + +class JavaExpansionProducer constructor(val basePackageName: String) : FileProducer { + + override fun produceFiles(): List { + return listOf( + generateCollectionHelper(), + expansionDsl() + ) + } + + private fun expansionDsl() : TemplateFile { + val content = """ + |package ${basePackageName.toJavaPackage()}.predicates.expansion; + | + |import io.vrap.rmf.base.client.Builder; + |import io.vrap.rmf.base.client.utils.Generated; + |import java.util.List; + | + |<<${JavaSubTemplates.generatedAnnotation}>> + |public interface ExpansionDsl extends Builder { + | public List getPath(); + | + | @Override + | public default String build() { + | return String.join(".", getPath()); + | } + |} + """.trimMargin().keepAngleIndent() + + return TemplateFile( + relativePath = "$basePackageName.predicates.expansion.ExpansionDsl".replace(".", "/") + ".java", + content = content + ) + } + private fun generateCollectionHelper() : TemplateFile { + val content = """ + |package ${basePackageName.toJavaPackage()}.predicates.expansion; + | + |import io.vrap.rmf.base.client.utils.Generated; + |import java.util.ArrayList; + |import java.util.Collections; + |import java.util.List; + | + |<<${JavaSubTemplates.generatedAnnotation}>> + |public class ExpansionUtil { + | + | public static List appendOne(final List list, T element) { + | final List arr = new ArrayList(list); + | arr.add(element); + | return Collections.unmodifiableList(arr); + | } + |} + """.trimMargin().keepAngleIndent() + + return TemplateFile( + relativePath = "$basePackageName.predicates.expansion.ExpansionUtil".replace(".", "/") + ".java", + content = content + ) + } +} diff --git a/languages/javalang/builder-renderer/java-builder-client/src/main/kotlin/io/vrap/codegen/languages/javalang/client/builder/predicates/JavaQueryPredicateModule.kt b/languages/javalang/builder-renderer/java-builder-client/src/main/kotlin/io/vrap/codegen/languages/javalang/client/builder/predicates/JavaQueryPredicateModule.kt index c281cd87d..e5b1436ef 100644 --- a/languages/javalang/builder-renderer/java-builder-client/src/main/kotlin/io/vrap/codegen/languages/javalang/client/builder/predicates/JavaQueryPredicateModule.kt +++ b/languages/javalang/builder-renderer/java-builder-client/src/main/kotlin/io/vrap/codegen/languages/javalang/client/builder/predicates/JavaQueryPredicateModule.kt @@ -1,6 +1,7 @@ package io.vrap.codegen.languages.javalang.client.builder.predicates +import io.vrap.codegen.languages.java.base.extensions.isExpandable import io.vrap.rmf.codegen.di.RamlGeneratorModule import io.vrap.rmf.codegen.di.Module import io.vrap.rmf.codegen.rendering.* @@ -11,6 +12,14 @@ object JavaQueryPredicateModule: Module { setOf( JavaQueryPredicateRenderer(generatorModule.providePackageName(), generatorModule.vrapTypeProvider()) ), generatorModule.allObjectTypes() + ), + ObjectTypeGenerator( + setOf( + JavaExpansionPredicateRenderer(generatorModule.providePackageName(), generatorModule.vrapTypeProvider()) + ), generatorModule.allObjectTypes().filter { it.isExpandable() } + ), + FileGenerator( + setOf(JavaExpansionProducer(generatorModule.providePackageName())) ) ) } diff --git a/languages/javalang/builder-renderer/java-builder-client/src/main/kotlin/io/vrap/codegen/languages/javalang/client/builder/producers/JavaModelClassFileProducer.kt b/languages/javalang/builder-renderer/java-builder-client/src/main/kotlin/io/vrap/codegen/languages/javalang/client/builder/producers/JavaModelClassFileProducer.kt index f3a474fac..58ebaf1af 100644 --- a/languages/javalang/builder-renderer/java-builder-client/src/main/kotlin/io/vrap/codegen/languages/javalang/client/builder/producers/JavaModelClassFileProducer.kt +++ b/languages/javalang/builder-renderer/java-builder-client/src/main/kotlin/io/vrap/codegen/languages/javalang/client/builder/producers/JavaModelClassFileProducer.kt @@ -44,8 +44,8 @@ class JavaModelClassFileProducer constructor(override val vrapTypeProvider: Vrap |${type.imports()} |import io.vrap.rmf.base.client.utils.Generated; |import io.vrap.rmf.base.client.ModelBase; - |import javax.validation.Valid; - |import javax.validation.constraints.NotNull; + |import jakarta.validation.Valid; + |import jakarta.validation.constraints.NotNull; |import java.util.*; |import java.time.*; | @@ -331,7 +331,7 @@ class JavaModelClassFileProducer constructor(override val vrapTypeProvider: Vrap | * create instance with all properties | */ |@JsonCreator - |${vrapType.simpleClassName}Impl(${constructorArguments.escapeAll()}) { + |${if(constructorArguments.isEmpty()) "public " else ""}${vrapType.simpleClassName}Impl(${constructorArguments.escapeAll()}) { | <$propertiesAssignment> | <$discriminatorAssignment> |} diff --git a/languages/javalang/builder-renderer/java-builder-client/src/main/kotlin/io/vrap/codegen/languages/javalang/client/builder/requests/JavaHttpRequestRenderer.kt b/languages/javalang/builder-renderer/java-builder-client/src/main/kotlin/io/vrap/codegen/languages/javalang/client/builder/requests/JavaHttpRequestRenderer.kt index fdb91eb4b..fe5c252f9 100644 --- a/languages/javalang/builder-renderer/java-builder-client/src/main/kotlin/io/vrap/codegen/languages/javalang/client/builder/requests/JavaHttpRequestRenderer.kt +++ b/languages/javalang/builder-renderer/java-builder-client/src/main/kotlin/io/vrap/codegen/languages/javalang/client/builder/requests/JavaHttpRequestRenderer.kt @@ -327,7 +327,7 @@ class JavaHttpRequestRenderer constructor(override val vrapTypeProvider: VrapTyp pathArguments.forEach { stringFormat = stringFormat.replace(it, "%s") } val stringFormatArgs = pathArguments .map { it.replace("{", "").replace("}", "") } - .map { "this.$it" } + .map { "encodePathParam(this.$it)" } .joinToString(separator = ", ") stringFormat = stringFormat.trimStart('/') val bodyName : String? = if(this.bodies != null && this.bodies.isNotEmpty()){ diff --git a/languages/javalang/java-base/src/main/kotlin/io/vrap/codegen/languages/java/base/extensions/JavaVrapExtensions.kt b/languages/javalang/java-base/src/main/kotlin/io/vrap/codegen/languages/java/base/extensions/JavaVrapExtensions.kt index a644b5859..8b59c901b 100644 --- a/languages/javalang/java-base/src/main/kotlin/io/vrap/codegen/languages/java/base/extensions/JavaVrapExtensions.kt +++ b/languages/javalang/java-base/src/main/kotlin/io/vrap/codegen/languages/java/base/extensions/JavaVrapExtensions.kt @@ -37,6 +37,12 @@ fun String.toScalarType(): String { return t } +fun AnyType.isExpandable() : Boolean { + val anno = this.getAnnotation("expandable", true) + return (anno != null && (anno.value as BooleanInstance).value) +} + + fun VrapType.fullClassName(unboxed: Boolean = false): String { return when (this) { is VrapAnyType -> this.baseType diff --git a/languages/javalang/java-renderer/src/main/kotlin/io/vrap/codegen/languages/javalang/model/JavaObjectTypeRenderer.kt b/languages/javalang/java-renderer/src/main/kotlin/io/vrap/codegen/languages/javalang/model/JavaObjectTypeRenderer.kt index 6a9d31c66..52c99918e 100644 --- a/languages/javalang/java-renderer/src/main/kotlin/io/vrap/codegen/languages/javalang/model/JavaObjectTypeRenderer.kt +++ b/languages/javalang/java-renderer/src/main/kotlin/io/vrap/codegen/languages/javalang/model/JavaObjectTypeRenderer.kt @@ -30,9 +30,9 @@ class JavaObjectTypeRenderer constructor(override val vrapTypeProvider: VrapType |${type.imports()} |import com.fasterxml.jackson.annotation.*; |import io.vrap.rmf.base.client.utils.Generated; - |import javax.validation.Valid; - |import javax.validation.constraints.NotNull; - |import javax.validation.constraints.Size; + |import jakarta.validation.Valid; + |import jakarta.validation.constraints.NotNull; + |import jakarta.validation.constraints.Size; |import java.util.*; |import org.apache.commons.lang3.builder.EqualsBuilder; |import org.apache.commons.lang3.builder.HashCodeBuilder; diff --git a/languages/php/src/main/kotlin/io/vrap/codegen/languages/php/base/PhpBaseFileProducer.kt b/languages/php/src/main/kotlin/io/vrap/codegen/languages/php/base/PhpBaseFileProducer.kt index 16a2f70b8..0175d973b 100644 --- a/languages/php/src/main/kotlin/io/vrap/codegen/languages/php/base/PhpBaseFileProducer.kt +++ b/languages/php/src/main/kotlin/io/vrap/codegen/languages/php/base/PhpBaseFileProducer.kt @@ -2752,7 +2752,19 @@ class PhpBaseFileProducer constructor(val api: Api, @BasePackageName val package | | /** @psalm-var string */ | private $!cacheKey; - | + | /** + | * The constructor of the ClientCredentials class. + | * + | * @param string $!clientId + | * The client id. + | * @param string $!clientSecret + | * The client secret. + | * @param string $!scope + | * Provide the scope when you want to request a specific ones for the client. + | * Can be omitted to use all scopes of the oauth client. + | * Format: `:`. + | * Example: `manage_products:project1`. + | */ | public function __construct(string $!clientId, string $!clientSecret, string $!scope = null) | { | $!this->clientId = $!clientId; diff --git a/languages/python/src/main/kotlin/io/vrap/codegen/languages/python/client/ApiRootFileProducer.kt b/languages/python/src/main/kotlin/io/vrap/codegen/languages/python/client/ApiRootFileProducer.kt index 9c943a38e..323745ce2 100644 --- a/languages/python/src/main/kotlin/io/vrap/codegen/languages/python/client/ApiRootFileProducer.kt +++ b/languages/python/src/main/kotlin/io/vrap/codegen/languages/python/client/ApiRootFileProducer.kt @@ -30,14 +30,14 @@ class ApiRootFileProducer constructor( content = """| |$pyGeneratedComment |${type.imports("client")} - |from commercetools.client import BaseClient + |from commercetools.base_client import BaseClient | | |class Client(BaseClient): | | def __init__(self, *args, **kwargs): | kwargs.setdefault("url", "${api.baseUri?.template}") - | super().__init__(self, **kwargs) + | super().__init__(**kwargs) | | <${type.subResources("self").escapeAll()}> | diff --git a/languages/ramldoc/src/main/kotlin/io/vrap/codegen/languages/ramldoc/extensions/VrapExtensions.kt b/languages/ramldoc/src/main/kotlin/io/vrap/codegen/languages/ramldoc/extensions/VrapExtensions.kt index 8a6ddf5da..0c150039f 100644 --- a/languages/ramldoc/src/main/kotlin/io/vrap/codegen/languages/ramldoc/extensions/VrapExtensions.kt +++ b/languages/ramldoc/src/main/kotlin/io/vrap/codegen/languages/ramldoc/extensions/VrapExtensions.kt @@ -173,9 +173,17 @@ fun AnyType.renderType(withDescription: Boolean = true): String { } else { "" } + val displayName = if ((this.isInlineType || this.isScalar()) && this.displayName?.value.isNullOrBlank().not()) { + """ + | + |displayName: ${this.displayName.value.trim()} + """.trimMargin() + } else { + "" + } return """ |${this.renderTypeFacet()} - |$builtinType + |$builtinType$displayName |$description """.trimMargin().trimEnd() } diff --git a/languages/ramldoc/src/test/kotlin/io/vrap/codegen/languages/ramldoc/TestCodeGenerator.kt b/languages/ramldoc/src/test/kotlin/io/vrap/codegen/languages/ramldoc/TestCodeGenerator.kt index 73b8fbc29..ff0227de3 100644 --- a/languages/ramldoc/src/test/kotlin/io/vrap/codegen/languages/ramldoc/TestCodeGenerator.kt +++ b/languages/ramldoc/src/test/kotlin/io/vrap/codegen/languages/ramldoc/TestCodeGenerator.kt @@ -149,6 +149,7 @@ class TestCodeGenerator { FOO: type: string (builtinType): string + displayName: Foo required: true body: application/json: diff --git a/languages/ramldoc/src/test/resources/curlexample.raml b/languages/ramldoc/src/test/resources/curlexample.raml index 6caadfedb..ee36da856 100644 --- a/languages/ramldoc/src/test/resources/curlexample.raml +++ b/languages/ramldoc/src/test/resources/curlexample.raml @@ -37,6 +37,7 @@ types: headers: FOO: type: string + displayName: Foo required: true responses: 200: diff --git a/languages/typescript/src/main/kotlin/io/vrap/codegen/languages/typescript/client/files_producers/ClientFileProducer.kt b/languages/typescript/src/main/kotlin/io/vrap/codegen/languages/typescript/client/files_producers/ClientFileProducer.kt index def77b07e..39b167321 100644 --- a/languages/typescript/src/main/kotlin/io/vrap/codegen/languages/typescript/client/files_producers/ClientFileProducer.kt +++ b/languages/typescript/src/main/kotlin/io/vrap/codegen/languages/typescript/client/files_producers/ClientFileProducer.kt @@ -59,6 +59,7 @@ export type ClientResponse = { body: T; statusCode?: number; headers?: Object; + originalRequest?: ClientRequest; }; export type executeRequest = (request: ClientRequest) => Promise diff --git a/languages/typescript/src/main/kotlin/io/vrap/codegen/languages/typescript/client/files_producers/IndexFileProducer.kt b/languages/typescript/src/main/kotlin/io/vrap/codegen/languages/typescript/client/files_producers/IndexFileProducer.kt index d89d27637..00986b474 100644 --- a/languages/typescript/src/main/kotlin/io/vrap/codegen/languages/typescript/client/files_producers/IndexFileProducer.kt +++ b/languages/typescript/src/main/kotlin/io/vrap/codegen/languages/typescript/client/files_producers/IndexFileProducer.kt @@ -37,6 +37,8 @@ class IndexFileProducer constructor( |//Common package |export * from '${clientConstants.commonTypesPackage}' |export * from '${clientConstants.middlewarePackage}' + |export * from '${clientConstants.requestUtilsPackage}' + |export * from '${clientConstants.uriUtilsPackage}' """.trimMargin() )) diff --git a/node/rmf-codegen/package.json b/node/rmf-codegen/package.json index e52b23b91..ca1c236e0 100644 --- a/node/rmf-codegen/package.json +++ b/node/rmf-codegen/package.json @@ -1,6 +1,6 @@ { "name": "@commercetools-docs/rmf-codegen", - "version": "13.32.0", + "version": "13.35.0", "description": "Provides RMF-Codegen to javascript projects", "license": "MIT", "homepage": "https://github.com/commercetools/rmf-codegen", diff --git a/scripts/install.sh b/scripts/install.sh index 529bc69bc..69fe5419e 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -2,7 +2,7 @@ set -e -CODEGEN_VERSION=${VRAP_VERSION:-"1.0.0-20240221102044"} +CODEGEN_VERSION=${VRAP_VERSION:-"1.0.0-20240806090705"} CLI_HOME=~/.rmf-cli LIB_FOLDER=$CLI_HOME/lib JAR_FILE_PATH=$LIB_FOLDER/codegen-cli-${CODEGEN_VERSION}.jar diff --git a/scripts/setup-signing-key.sh b/scripts/setup-signing-key.sh index 76b85e702..4b7b6012a 100755 --- a/scripts/setup-signing-key.sh +++ b/scripts/setup-signing-key.sh @@ -14,16 +14,16 @@ echo "Decrypt signing secrets" gcloud kms decrypt \ --project=commercetools-platform \ --location=global \ - --keyring=teamcity \ - --key=jvm-sdk \ + --keyring=devtooling \ + --key=java-sdk-v2 \ --ciphertext-file=signing_key.enc \ --plaintext-file=signing_key.asc gcloud kms decrypt \ --project=commercetools-platform \ --location=global \ - --keyring=teamcity \ - --key=jvm-sdk \ + --keyring=devtooling \ + --key=java-sdk-v2 \ --ciphertext-file=signing_passphrase.enc \ --plaintext-file=signing_passphrase.txt @@ -31,12 +31,13 @@ gcloud kms decrypt \ set +e echo "Importing the signing key" gpg --import --no-tty --batch --yes signing_key.asc +echo " - done" set -e # List available GPG keys gpg -K -KEYNAME=`gpg --with-colons --keyid-format long --list-keys automation@commercetools.de | grep fpr | cut -d ':' -f 10` +KEYNAME=`gpg --with-colons --keyid-format long --list-keys devtooling@commercetools.com | grep fpr | cut -d ':' -f 10` mkdir -p ~/.gradle touch ~/.gradle/gradle.properties 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..15f37984b 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 @@ -37,6 +39,8 @@ import io.vrap.codegen.languages.java.base.PlantUmlBaseTypes import io.vrap.codegen.languages.javalang.client.builder.predicates.JavaQueryPredicateModule import io.vrap.codegen.languages.javalang.plantuml.PlantUmlModule import io.vrap.codegen.languages.ramldoc.model.MarkdownModelModule +import io.vrap.codegen.languages.typescript.joi.JoiBaseTypes +import io.vrap.codegen.languages.typescript.joi.JoiModule import io.vrap.rmf.codegen.CodeGeneratorConfig import io.vrap.rmf.codegen.di.* import io.vrap.rmf.codegen.io.DataSink @@ -77,9 +81,11 @@ enum class GenerationTarget { OAS, PYTHON_CLIENT, PLANTUML, - DOC_MARKDOWN + DOC_MARKDOWN, + BRUNO, + TYPESCRIPT_JOI_VALIDATOR } -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, TYPESCRIPT_JOI_VALIDATOR" @CommandLine.Command(name = "generate",description = ["Generate source code from a RAML specification."]) class GenerateSubcommand : Callable { @@ -244,6 +250,10 @@ class GenerateSubcommand : Callable { val generatorModule = RamlGeneratorModule(apiProvider, generatorConfig, TypeScriptBaseTypes, dataSink = sink) RamlGeneratorComponent(generatorModule, TypescriptTestModule) } + GenerationTarget.TYPESCRIPT_JOI_VALIDATOR -> { + val generatorModule = RamlGeneratorModule(apiProvider, generatorConfig, JoiBaseTypes, dataSink = sink) + RamlGeneratorComponent(generatorModule, JoiModule) + } GenerationTarget.PHP_CLIENT -> { val generatorModule = RamlGeneratorModule(apiProvider, generatorConfig, PhpBaseTypes, dataSink = sink) RamlGeneratorComponent(generatorModule, PhpModelModule) @@ -272,6 +282,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,