From 874da24433252f3ef17a9bf8982d3b2f33329426 Mon Sep 17 00:00:00 2001 From: Anna Shipil Date: Fri, 29 May 2026 10:50:10 +0200 Subject: [PATCH 01/45] DEVX-796: updating jackson version --- .../client/builder/model/JavaModelInterfaceRenderer.kt | 6 +++--- .../client/builder/producers/JavaModelClassFileProducer.kt | 4 ++-- .../client/builder/requests/JavaHttpRequestRenderer.kt | 2 +- .../builder/requests/JavaStringHttpRequestRenderer.kt | 2 +- .../languages/java/base/extensions/JavaMethodExtensions.kt | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) 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 e19828e9..0904931a 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 @@ -50,7 +50,7 @@ class JavaModelInterfaceRenderer constructor(override val vrapTypeProvider: Vrap |${type.subclassImport()} | |import com.fasterxml.jackson.annotation.*; - |import com.fasterxml.jackson.databind.annotation.*; + |import tools.jackson.databind.annotation.*; |import io.vrap.rmf.base.client.utils.Generated; |import io.vrap.rmf.base.client.Accessor; |import jakarta.validation.Valid; @@ -118,8 +118,8 @@ class JavaModelInterfaceRenderer constructor(override val vrapTypeProvider: Vrap | * gives a TypeReference for usage with Jackson DataBind | * @return TypeReference | */ - |public static com.fasterxml.jackson.core.type.TypeReference<${vrapType.simpleClassName}> typeReference() { - | return new com.fasterxml.jackson.core.type.TypeReference<${vrapType.simpleClassName}>() { + |public static tools.jackson.core.type.TypeReference<${vrapType.simpleClassName}> typeReference() { + | return new tools.jackson.core.type.TypeReference<${vrapType.simpleClassName}>() { | @Override | public String toString() { | return "TypeReference<${vrapType.simpleClassName}>"; 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 cbc5dc00..44813ca9 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 @@ -50,8 +50,8 @@ class JavaModelClassFileProducer constructor(override val vrapTypeProvider: Vrap |import java.util.*; |import java.time.*; | - |import com.fasterxml.jackson.core.JsonProcessingException; - |import com.fasterxml.jackson.databind.annotation.*; + |import tools.jackson.core.exc.JacksonException; + |import import tools.jackson.databind.annotation.*; |import com.fasterxml.jackson.annotation.JsonAnySetter; |import com.fasterxml.jackson.annotation.JsonCreator; |import com.fasterxml.jackson.annotation.JsonIgnore; 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 fe5c252f..a740c166 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 @@ -78,7 +78,7 @@ class JavaHttpRequestRenderer constructor(override val vrapTypeProvider: VrapTyp |import java.util.stream.Collectors; |import java.util.concurrent.CompletableFuture; |import io.vrap.rmf.base.client.utils.Generated; - |import com.fasterxml.jackson.core.type.TypeReference; + |import tools.jackson.core.type.TypeReference; | |import javax.annotation.Nullable; | diff --git a/languages/javalang/builder-renderer/java-builder-client/src/main/kotlin/io/vrap/codegen/languages/javalang/client/builder/requests/JavaStringHttpRequestRenderer.kt b/languages/javalang/builder-renderer/java-builder-client/src/main/kotlin/io/vrap/codegen/languages/javalang/client/builder/requests/JavaStringHttpRequestRenderer.kt index 2a41e47d..dfc3c582 100644 --- a/languages/javalang/builder-renderer/java-builder-client/src/main/kotlin/io/vrap/codegen/languages/javalang/client/builder/requests/JavaStringHttpRequestRenderer.kt +++ b/languages/javalang/builder-renderer/java-builder-client/src/main/kotlin/io/vrap/codegen/languages/javalang/client/builder/requests/JavaStringHttpRequestRenderer.kt @@ -73,7 +73,7 @@ class JavaStringHttpRequestRenderer constructor(override val vrapTypeProvider: V |import java.util.stream.Collectors; |import java.util.concurrent.CompletableFuture; |import io.vrap.rmf.base.client.utils.Generated; - |import com.fasterxml.jackson.core.type.TypeReference; + |import tools.jackson.core.type.TypeReference; | |import javax.annotation.Nullable; | diff --git a/languages/javalang/java-base/src/main/kotlin/io/vrap/codegen/languages/java/base/extensions/JavaMethodExtensions.kt b/languages/javalang/java-base/src/main/kotlin/io/vrap/codegen/languages/java/base/extensions/JavaMethodExtensions.kt index 688896de..cd223272 100644 --- a/languages/javalang/java-base/src/main/kotlin/io/vrap/codegen/languages/java/base/extensions/JavaMethodExtensions.kt +++ b/languages/javalang/java-base/src/main/kotlin/io/vrap/codegen/languages/java/base/extensions/JavaMethodExtensions.kt @@ -13,7 +13,7 @@ fun Method.javaReturnType(vrapTypeProvider: VrapTypeProvider) : String { if(returnType is VrapObjectType) { return "${returnType.`package`.toJavaPackage()}.${returnType.simpleClassName}" } - return "com.fasterxml.jackson.databind.JsonNode" + return "tools.jackson.databind.JsonNode" } fun Method.toStringRequestName(): String { From 6cc7f240877fcc146adefefcd53e297a9d7b0305 Mon Sep 17 00:00:00 2001 From: Auto Mation Date: Tue, 16 Sep 2025 08:46:23 +0000 Subject: [PATCH 02/45] TASK: Updating version in README --- scripts/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/install.sh b/scripts/install.sh index 5a0ff11e..6e74b4c1 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -2,7 +2,7 @@ set -e -CODEGEN_VERSION=${VRAP_VERSION:-"1.0.0-20250827125749"} +CODEGEN_VERSION=${VRAP_VERSION:-"1.0.0-20250916083648"} CLI_HOME=~/.rmf-cli LIB_FOLDER=$CLI_HOME/lib JAR_FILE_PATH=$LIB_FOLDER/codegen-cli-${CODEGEN_VERSION}.jar From 609bb88a6c153d2722cd02d520e446dee0669110 Mon Sep 17 00:00:00 2001 From: Jens Schulze Date: Tue, 16 Sep 2025 11:40:09 +0200 Subject: [PATCH 03/45] Revert "TASK: Updating version in README" This reverts commit bc30b5dba59323bc4faf54b7ea12f5fa1fac37e5. --- scripts/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/install.sh b/scripts/install.sh index 6e74b4c1..5a0ff11e 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -2,7 +2,7 @@ set -e -CODEGEN_VERSION=${VRAP_VERSION:-"1.0.0-20250916083648"} +CODEGEN_VERSION=${VRAP_VERSION:-"1.0.0-20250827125749"} CLI_HOME=~/.rmf-cli LIB_FOLDER=$CLI_HOME/lib JAR_FILE_PATH=$LIB_FOLDER/codegen-cli-${CODEGEN_VERSION}.jar From 40d6070a7e9898a8315a38a56bb1c3557f93d383 Mon Sep 17 00:00:00 2001 From: Jens Schulze Date: Thu, 18 Sep 2025 14:24:12 +0200 Subject: [PATCH 04/45] Update install.sh --- scripts/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/install.sh b/scripts/install.sh index 5a0ff11e..6e74b4c1 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -2,7 +2,7 @@ set -e -CODEGEN_VERSION=${VRAP_VERSION:-"1.0.0-20250827125749"} +CODEGEN_VERSION=${VRAP_VERSION:-"1.0.0-20250916083648"} CLI_HOME=~/.rmf-cli LIB_FOLDER=$CLI_HOME/lib JAR_FILE_PATH=$LIB_FOLDER/codegen-cli-${CODEGEN_VERSION}.jar From baa7a9b31a4516d824aebc5b48f225b501e880c4 Mon Sep 17 00:00:00 2001 From: Jens Schulze Date: Thu, 25 Sep 2025 13:19:10 +0200 Subject: [PATCH 05/45] fix CSharp and Java code generator for filterAttributes --- .../languages/csharp/extensions/CsharpVrapExtensions.kt | 3 +-- .../vrap/codegen/languages/csharp/model/CsharpTraitRenderer.kt | 2 +- .../languages/csharp/requests/CsharpHttpRequestRenderer.kt | 2 +- .../codegen/languages/csharp/test/CsharpRequestTestRenderer.kt | 3 ++- .../javalang/client/builder/model/JavaTraitRenderer.kt | 2 +- .../client/builder/requests/JavaHttpRequestRenderer.kt | 2 +- .../javalang/client/builder/test/JavaRequestTestRenderer.kt | 3 ++- .../languages/java/base/extensions/JavaVrapExtensions.kt | 2 +- 8 files changed, 10 insertions(+), 9 deletions(-) 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 d15f27bd..a73e8791 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 @@ -168,7 +168,7 @@ fun QueryParameter.methodName(): String { val paramName = o.value.stream().filter { propertyValue -> propertyValue.name == "paramName" }.findFirst().orElse(null).value as StringInstance return "With" + StringCaseFormat.UPPER_CAMEL_CASE.apply(paramName.value) } - return "With" + StringCaseFormat.UPPER_CAMEL_CASE.apply(this.name.replace(".", "-")) + return "With" + StringCaseFormat.UPPER_CAMEL_CASE.apply(this.name.replace(".", "-").replace("[", "-").replace("]", "")) } fun Property.deprecated() : Boolean { @@ -188,4 +188,3 @@ fun Property.markDeprecated() : Boolean { 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/CsharpTraitRenderer.kt b/languages/csharp/src/main/kotlin/io/vrap/codegen/languages/csharp/model/CsharpTraitRenderer.kt index d441c145..7c55fc64 100644 --- a/languages/csharp/src/main/kotlin/io/vrap/codegen/languages/csharp/model/CsharpTraitRenderer.kt +++ b/languages/csharp/src/main/kotlin/io/vrap/codegen/languages/csharp/model/CsharpTraitRenderer.kt @@ -109,6 +109,6 @@ class CsharpTraitRenderer constructor(override val vrapTypeProvider: VrapTypePr } private fun QueryParameter.fieldName(): String { - return StringCaseFormat.LOWER_CAMEL_CASE.apply(this.name.replace(".", "-")) + return StringCaseFormat.LOWER_CAMEL_CASE.apply(this.name.replace(".", "-").replace("[", "-").replace("]", "")) } } diff --git a/languages/csharp/src/main/kotlin/io/vrap/codegen/languages/csharp/requests/CsharpHttpRequestRenderer.kt b/languages/csharp/src/main/kotlin/io/vrap/codegen/languages/csharp/requests/CsharpHttpRequestRenderer.kt index a41bb2d4..09716452 100644 --- a/languages/csharp/src/main/kotlin/io/vrap/codegen/languages/csharp/requests/CsharpHttpRequestRenderer.kt +++ b/languages/csharp/src/main/kotlin/io/vrap/codegen/languages/csharp/requests/CsharpHttpRequestRenderer.kt @@ -187,7 +187,7 @@ class CsharpHttpRequestRenderer constructor(override val vrapTypeProvider: VrapT } private fun QueryParameter.fieldName(): String { - return StringCaseFormat.LOWER_CAMEL_CASE.apply(this.name.replace(".", "-")) + return StringCaseFormat.LOWER_CAMEL_CASE.apply(this.name.replace(".", "-").replace("[", "-").replace("]", "")) } private fun QueryParameter.fieldNameAsString(type: String): String { diff --git a/languages/csharp/src/main/kotlin/io/vrap/codegen/languages/csharp/test/CsharpRequestTestRenderer.kt b/languages/csharp/src/main/kotlin/io/vrap/codegen/languages/csharp/test/CsharpRequestTestRenderer.kt index 4eb099da..85b13b4d 100644 --- a/languages/csharp/src/main/kotlin/io/vrap/codegen/languages/csharp/test/CsharpRequestTestRenderer.kt +++ b/languages/csharp/src/main/kotlin/io/vrap/codegen/languages/csharp/test/CsharpRequestTestRenderer.kt @@ -14,6 +14,7 @@ import io.vrap.rmf.codegen.types.VrapTypeProvider import io.vrap.rmf.raml.model.resources.Method import io.vrap.rmf.raml.model.resources.Resource import io.vrap.rmf.raml.model.types.* +import java.net.URLEncoder import kotlin.random.Random class CsharpRequestTestRenderer constructor(override val vrapTypeProvider: VrapTypeProvider, private val basePackagePrefix: String): ResourceRenderer, CsharpEObjectTypeExtensions, CsharpObjectTypeExtensions { @@ -135,7 +136,7 @@ class CsharpRequestTestRenderer constructor(override val vrapTypeProvider: VrapT NumberFormat.FLOAT -> r.nextFloat().toString() else -> r.nextInt(1, 10) } - else -> name + else -> URLEncoder.encode(name, "UTF-8") } } diff --git a/languages/javalang/builder-renderer/java-builder-client/src/main/kotlin/io/vrap/codegen/languages/javalang/client/builder/model/JavaTraitRenderer.kt b/languages/javalang/builder-renderer/java-builder-client/src/main/kotlin/io/vrap/codegen/languages/javalang/client/builder/model/JavaTraitRenderer.kt index 1666d0ab..5e4c2ff7 100644 --- a/languages/javalang/builder-renderer/java-builder-client/src/main/kotlin/io/vrap/codegen/languages/javalang/client/builder/model/JavaTraitRenderer.kt +++ b/languages/javalang/builder-renderer/java-builder-client/src/main/kotlin/io/vrap/codegen/languages/javalang/client/builder/model/JavaTraitRenderer.kt @@ -146,6 +146,6 @@ class JavaTraitRenderer constructor(override val vrapTypeProvider: VrapTypeProvi } private fun QueryParameter.fieldName(): String { - return StringCaseFormat.LOWER_CAMEL_CASE.apply(this.name.replace(".", "-")) + return StringCaseFormat.LOWER_CAMEL_CASE.apply(this.name.replace(".", "-").replace("[", "-").replace("]", "")) } } 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 a740c166..a3aa8b92 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 @@ -313,7 +313,7 @@ class JavaHttpRequestRenderer constructor(override val vrapTypeProvider: VrapTyp } private fun QueryParameter.fieldName(): String { - return StringCaseFormat.LOWER_CAMEL_CASE.apply(this.name.replace(".", "-")) + return StringCaseFormat.LOWER_CAMEL_CASE.apply(this.name.replace(".", "-").replace("[", "-").replace("]", "")) } private fun Method.pathArguments() : List { diff --git a/languages/javalang/builder-renderer/java-builder-client/src/main/kotlin/io/vrap/codegen/languages/javalang/client/builder/test/JavaRequestTestRenderer.kt b/languages/javalang/builder-renderer/java-builder-client/src/main/kotlin/io/vrap/codegen/languages/javalang/client/builder/test/JavaRequestTestRenderer.kt index ccdb8f3c..24b09e27 100644 --- a/languages/javalang/builder-renderer/java-builder-client/src/main/kotlin/io/vrap/codegen/languages/javalang/client/builder/test/JavaRequestTestRenderer.kt +++ b/languages/javalang/builder-renderer/java-builder-client/src/main/kotlin/io/vrap/codegen/languages/javalang/client/builder/test/JavaRequestTestRenderer.kt @@ -14,6 +14,7 @@ import io.vrap.rmf.codegen.types.VrapTypeProvider import io.vrap.rmf.raml.model.resources.Method import io.vrap.rmf.raml.model.resources.Resource import io.vrap.rmf.raml.model.types.* +import java.net.URLEncoder import kotlin.random.Random class JavaRequestTestRenderer constructor(override val vrapTypeProvider: VrapTypeProvider): ResourceRenderer, JavaEObjectTypeExtensions, JavaObjectTypeExtensions { @@ -221,7 +222,7 @@ class JavaRequestTestRenderer constructor(override val vrapTypeProvider: VrapTyp NumberFormat.FLOAT -> r.nextFloat() else -> r.nextInt(1, 10) } - else -> name + else -> URLEncoder.encode(name, "UTF-8") } } 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 8b59c901..7f4f43e3 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 @@ -116,5 +116,5 @@ fun QueryParameter.methodName(): String { val paramName = o.value.stream().filter { propertyValue -> propertyValue.name == "paramName" }.findFirst().orElse(null).value as StringInstance return "with" + StringCaseFormat.UPPER_CAMEL_CASE.apply(paramName.value) } - return "with" + StringCaseFormat.UPPER_CAMEL_CASE.apply(this.name.replace(".", "-")) + return "with" + StringCaseFormat.UPPER_CAMEL_CASE.apply(this.name.replace(".", "-").replace("[", "-").replace("]", "")) } From a0d004404c7cb4101a9d43ab47ff569880d7b87b Mon Sep 17 00:00:00 2001 From: Auto Mation Date: Thu, 25 Sep 2025 11:51:38 +0000 Subject: [PATCH 06/45] TASK: Updating version in README --- scripts/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/install.sh b/scripts/install.sh index 6e74b4c1..f3857b57 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -2,7 +2,7 @@ set -e -CODEGEN_VERSION=${VRAP_VERSION:-"1.0.0-20250916083648"} +CODEGEN_VERSION=${VRAP_VERSION:-"1.0.0-20250925114258"} CLI_HOME=~/.rmf-cli LIB_FOLDER=$CLI_HOME/lib JAR_FILE_PATH=$LIB_FOLDER/codegen-cli-${CODEGEN_VERSION}.jar From 47f8d58c624bae301b25b35baf4879f0186b8463 Mon Sep 17 00:00:00 2001 From: Jens Schulze Date: Mon, 29 Sep 2025 08:40:34 +0200 Subject: [PATCH 07/45] fix PHP & TS generator for filter attributes --- .../io/vrap/codegen/languages/php/model/PhpTraitRenderer.kt | 2 +- .../languages/typescript/test/TypescriptRequestTestRenderer.kt | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/languages/php/src/main/kotlin/io/vrap/codegen/languages/php/model/PhpTraitRenderer.kt b/languages/php/src/main/kotlin/io/vrap/codegen/languages/php/model/PhpTraitRenderer.kt index 61352e43..bcd7c23f 100644 --- a/languages/php/src/main/kotlin/io/vrap/codegen/languages/php/model/PhpTraitRenderer.kt +++ b/languages/php/src/main/kotlin/io/vrap/codegen/languages/php/model/PhpTraitRenderer.kt @@ -77,7 +77,7 @@ class PhpTraitRenderer constructor(override val vrapTypeProvider: VrapTypeProvid } private fun QueryParameter.fieldName(): String { - return StringCaseFormat.LOWER_CAMEL_CASE.apply(this.name.replace(".", "-")) + return StringCaseFormat.LOWER_CAMEL_CASE.apply(this.name.replace(".", "-").replace("[", "-").replace("]", "")) } private fun Trait.markDeprecated() : Boolean { diff --git a/languages/typescript/src/main/kotlin/io/vrap/codegen/languages/typescript/test/TypescriptRequestTestRenderer.kt b/languages/typescript/src/main/kotlin/io/vrap/codegen/languages/typescript/test/TypescriptRequestTestRenderer.kt index 3e007975..3dff0259 100644 --- a/languages/typescript/src/main/kotlin/io/vrap/codegen/languages/typescript/test/TypescriptRequestTestRenderer.kt +++ b/languages/typescript/src/main/kotlin/io/vrap/codegen/languages/typescript/test/TypescriptRequestTestRenderer.kt @@ -15,6 +15,7 @@ import io.vrap.rmf.codegen.types.VrapTypeProvider 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.util.regex.Pattern import kotlin.random.Random @@ -99,7 +100,7 @@ class TypescriptRequestTestRenderer constructor(override val vrapTypeProvider: V paramName = "\"${placeholderTemplate.value.replace("<${placeholder.value}>", placeholder.value)}\"" methodValue = "$paramName" } - else if(paramName.contains(".")) + else if(paramName.contains(".") || paramName.contains("[")) { paramName = "\"${paramName}\"" } From 8ec7f34efb1fd7b6866a0a5530f8dd69c7d2407c Mon Sep 17 00:00:00 2001 From: Auto Mation Date: Mon, 29 Sep 2025 07:07:23 +0000 Subject: [PATCH 08/45] TASK: Updating version in README --- scripts/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/install.sh b/scripts/install.sh index f3857b57..3400cbd6 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -2,7 +2,7 @@ set -e -CODEGEN_VERSION=${VRAP_VERSION:-"1.0.0-20250925114258"} +CODEGEN_VERSION=${VRAP_VERSION:-"1.0.0-20250929065732"} CLI_HOME=~/.rmf-cli LIB_FOLDER=$CLI_HOME/lib JAR_FILE_PATH=$LIB_FOLDER/codegen-cli-${CODEGEN_VERSION}.jar From 203a5a5d8a6bcbb85a8e90f7a598c1bf2cf15a2b Mon Sep 17 00:00:00 2001 From: Jens Schulze Date: Mon, 29 Sep 2025 11:02:57 +0200 Subject: [PATCH 09/45] fix query parameter URL encoding --- .../vrap/codegen/languages/php/test/PhpRequestTestRenderer.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/languages/php/src/main/kotlin/io/vrap/codegen/languages/php/test/PhpRequestTestRenderer.kt b/languages/php/src/main/kotlin/io/vrap/codegen/languages/php/test/PhpRequestTestRenderer.kt index 8af4405a..1486457c 100644 --- a/languages/php/src/main/kotlin/io/vrap/codegen/languages/php/test/PhpRequestTestRenderer.kt +++ b/languages/php/src/main/kotlin/io/vrap/codegen/languages/php/test/PhpRequestTestRenderer.kt @@ -19,6 +19,7 @@ import io.vrap.rmf.raml.model.types.ObjectInstance import io.vrap.rmf.raml.model.types.QueryParameter import io.vrap.rmf.raml.model.types.StringInstance import org.eclipse.emf.ecore.EObject +import java.net.URLEncoder class PhpRequestTestRenderer constructor(api: Api, vrapTypeProvider: VrapTypeProvider, clientConstants: ClientConstants) : ResourceRenderer, AbstractRequestBuilder(api, vrapTypeProvider, clientConstants), EObjectTypeExtensions { private val resourcePackage = "Resource" @@ -243,7 +244,7 @@ class PhpRequestTestRenderer constructor(api: Api, vrapTypeProvider: VrapTypePro | <<${builderChain.joinToString("\n->", "->")}>>; | }, | '${method.method}', - | '${resource.fullUri.expand(resource.fullUriParameters.map { it.name to "test_${it.name}" }.toMap()).trimStart('/')}?${paramName}=${paramName}', + | '${resource.fullUri.expand(resource.fullUriParameters.map { it.name to "test_${it.name}" }.toMap()).trimStart('/')}?${URLEncoder.encode(paramName, "UTF-8")}=${URLEncoder.encode(paramName, "UTF-8")}', |] """.trimMargin() } From 76b2cd46c2c0ce5ee4a5c4363fdfcc4f3718171d Mon Sep 17 00:00:00 2001 From: Auto Mation Date: Mon, 29 Sep 2025 09:14:30 +0000 Subject: [PATCH 10/45] TASK: Updating version in README --- scripts/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/install.sh b/scripts/install.sh index 3400cbd6..b97ff742 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -2,7 +2,7 @@ set -e -CODEGEN_VERSION=${VRAP_VERSION:-"1.0.0-20250929065732"} +CODEGEN_VERSION=${VRAP_VERSION:-"1.0.0-20250929090531"} CLI_HOME=~/.rmf-cli LIB_FOLDER=$CLI_HOME/lib JAR_FILE_PATH=$LIB_FOLDER/codegen-cli-${CODEGEN_VERSION}.jar From 315c3c8c41efea8517e4f30a3317eeb40ff75538 Mon Sep 17 00:00:00 2001 From: Jens Schulze Date: Fri, 19 Dec 2025 12:29:10 +0100 Subject: [PATCH 11/45] fix TS test generator --- .../typescript/test/TypescriptRequestTestRenderer.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/languages/typescript/src/main/kotlin/io/vrap/codegen/languages/typescript/test/TypescriptRequestTestRenderer.kt b/languages/typescript/src/main/kotlin/io/vrap/codegen/languages/typescript/test/TypescriptRequestTestRenderer.kt index 3dff0259..d07ab82a 100644 --- a/languages/typescript/src/main/kotlin/io/vrap/codegen/languages/typescript/test/TypescriptRequestTestRenderer.kt +++ b/languages/typescript/src/main/kotlin/io/vrap/codegen/languages/typescript/test/TypescriptRequestTestRenderer.kt @@ -16,6 +16,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.util.StringCaseFormat +import java.net.URLEncoder import java.util.regex.Pattern import kotlin.random.Random @@ -91,7 +92,7 @@ class TypescriptRequestTestRenderer constructor(override val vrapTypeProvider: V var methodValue = parameter.template() var requiredParams = method.queryParameters.toMutableList().filter { p -> p.name != parameter.name && p.required } var requiredParamsStr: String = requiredParams.map { r -> "${r.name}:${queryParamValueString(r.name, r.type, Random(r.name.hashCode()))}" }.joinToString(", ") - var requiredParamsStrUrl: String = requiredParams.map { r -> "${r.name}:${queryParamValueString(r.name, r.type, Random(r.name.hashCode())).toString().replace("\"","")}" }.joinToString("&") + var requiredParamsStrUrl: String = requiredParams.map { r -> "${r.name}:${URLEncoder.encode(queryParamValueString(r.name, r.type, Random(r.name.hashCode())).toString().replace("\"",""), "UTF-8")}" }.joinToString("&") if (anno != null) { val o = anno.value as ObjectInstance @@ -111,7 +112,7 @@ class TypescriptRequestTestRenderer constructor(override val vrapTypeProvider: V return """ |{ | method: '${method.method}', - | uri: '/${resource.fullUri.expand(resource.fullUriParameters.map { it.name to "test_${it.name}" }.toMap()).trimStart('/')}?${paramName.replace("\"", "")}=${queryParamValueString(paramName, parameter.type, Random(paramName.hashCode())).toString().replace("\"","")}${ if(requiredParamsStrUrl!="") "&${requiredParamsStrUrl.replace(":","=")}" else ""}', + | uri: '/${resource.fullUri.expand(resource.fullUriParameters.map { it.name to "test_${it.name}" }.toMap()).trimStart('/')}?${URLEncoder.encode(paramName.replace("\"", ""), "UTF-8")}=${URLEncoder.encode(queryParamValueString(paramName, parameter.type, Random(paramName.hashCode())).toString().replace("\"",""), "UTF-8")}${ if(requiredParamsStrUrl!="") "&${requiredParamsStrUrl.replace(":","=")}" else ""}', | request: apiRoot | <<${builderChain.joinToString("\n.", ".")}>>, |} From 7af76c0b4ad6a717cb8d33a988a3736961f54d92 Mon Sep 17 00:00:00 2001 From: Auto Mation Date: Fri, 19 Dec 2025 11:57:38 +0000 Subject: [PATCH 12/45] TASK: Updating version in README --- scripts/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/install.sh b/scripts/install.sh index b97ff742..a36f1cfb 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -2,7 +2,7 @@ set -e -CODEGEN_VERSION=${VRAP_VERSION:-"1.0.0-20250929090531"} +CODEGEN_VERSION=${VRAP_VERSION:-"1.0.0-20251219114602"} CLI_HOME=~/.rmf-cli LIB_FOLDER=$CLI_HOME/lib JAR_FILE_PATH=$LIB_FOLDER/codegen-cli-${CODEGEN_VERSION}.jar From 82abb60ea564cca740c45e7ccbb7c2259948e2f2 Mon Sep 17 00:00:00 2001 From: FFawzy Date: Tue, 17 Feb 2026 20:39:38 +0100 Subject: [PATCH 13/45] feat: agent init draft 1 --- .../ctp-validator-rule-creator.agent.md | 224 ++++++++++++++++++ 1 file changed, 224 insertions(+) create mode 100644 .github/agents/ctp-validator-rule-creator.agent.md diff --git a/.github/agents/ctp-validator-rule-creator.agent.md b/.github/agents/ctp-validator-rule-creator.agent.md new file mode 100644 index 00000000..e0fac653 --- /dev/null +++ b/.github/agents/ctp-validator-rule-creator.agent.md @@ -0,0 +1,224 @@ +--- +name: ctp-validator-rule-creator +description: This agent creates new validation rules for the ctp-validators Kotlin project, including rule implementation, test cases, and test RAML files. +tools: ['read', 'edit', 'search', 'web'] +--- +# CTP Validator Rule Creator Agent + +You are a specialized agent for creating new validation rules in the ctp-validators Kotlin project. + +## Your Expertise + +You excel at: +1. Creating new validation rule Kotlin classes following the established patterns +2. Writing comprehensive test cases in Groovy +3. Creating test RAML files with both valid and invalid examples +4. Understanding the validation framework architecture + +## Project Structure + +- **Rule files location**: `ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/` +- **Test file location**: `ctp-validators/src/test/groovy/com/commercetools/rmf/validators/ValidatorRulesTest.groovy` +- **Test RAML files**: `ctp-validators/src/test/resources/` + +## Rule Implementation Pattern + +### 1. Rule File Structure (Kotlin) + +Every rule file must follow this pattern: + +```kotlin +package com.commercetools.rmf.validators + +import io.vrap.rmf.raml.model.types.* +import org.eclipse.emf.common.util.Diagnostic +import java.util.* + +@ValidatorSet +class YourRuleNameRule(severity: RuleSeverity, options: List? = null) : TypesRule(severity, options) { + + // Optional: excludes for properties that should be exempt from the rule + private val exclude: List = + (options?.filter { ruleOption -> ruleOption.type.lowercase(Locale.getDefault()) == RuleOptionType.EXCLUDE.toString() } + ?.map { ruleOption -> ruleOption.value }?.plus("") ?: defaultExcludes) + + // Override the appropriate case method based on what you're validating + // Common options: caseObjectType, caseProperty, caseStringType, etc. + override fun caseObjectType(type: ObjectType): List { + val validationResults: MutableList = ArrayList() + + // Your validation logic here + // Use: error(object, "message", args...) or create(object, "message", args...) + + return validationResults + } + + companion object : ValidatorFactory { + private val defaultExcludes by lazy { listOf("") } + + @JvmStatic + override fun create(options: List?): YourRuleNameRule { + return YourRuleNameRule(RuleSeverity.ERROR, options) + } + + @JvmStatic + override fun create(severity: RuleSeverity, options: List?): YourRuleNameRule { + return YourRuleNameRule(severity, options) + } + } +} +``` + +**Key Points:** +- Annotate with `@ValidatorSet` +- Extend `TypesRule(severity, options)` +- Implement companion object with `ValidatorFactory` +- Use `error()` or `create()` methods to generate diagnostics +- Support exclusion options if needed +- File name must be PascalCase ending with `Rule.kt` (e.g., `BooleanPropertyNameRule.kt`) + +### 2. Test Case Structure (Groovy) + +Add a test method to `ValidatorRulesTest.groovy`: + +```groovy +def "your rule name test"() { + when: + def validators = Arrays.asList(new TypesValidator(Arrays.asList(YourRuleNameRule.create(emptyList())))) + def uri = uriFromClasspath("/your-rule-name.raml") + def result = new RamlModelBuilder(validators).buildApi(uri) + then: + result.validationResults.size == X + result.validationResults[0].message == "Expected error message" + result.validationResults[1].message == "Another expected error message" +} +``` + +**With exclusions:** +```groovy +def "your rule name test with exclusions"() { + when: + def options = singletonList(new RuleOption(RuleOptionType.EXCLUDE.toString(), "TypeName:propertyName")) + def validators = Arrays.asList(new TypesValidator(Arrays.asList(YourRuleNameRule.create(options)))) + def uri = uriFromClasspath("/your-rule-name.raml") + def result = new RamlModelBuilder(validators).buildApi(uri) + then: + result.validationResults.size == X +} +``` + +### 3. Test RAML File Structure + +Create a comprehensive test RAML file at `ctp-validators/src/test/resources/your-rule-name.raml`: + +```raml +#%RAML 1.0 +title: your rule name + +annotationTypes: + package: string + sdkBaseUri: string + +baseUri: https://api.europe-west1.commercetools.com + +types: + InvalidExample: + (package): Common + type: object + properties: + # Properties that violate the rule with clear comments + badProperty: + description: xyz + type: string + + ValidExample: + (package): Common + type: object + properties: + # Properties that follow the rule with clear comments + goodProperty: + description: xyz + type: string +``` + +**Key Points:** +- File name must be kebab-case matching the test: `your-rule-name.raml` +- Include both invalid and valid examples +- Add descriptive comments explaining why each example is valid/invalid +- Use clear type names like `InvalidXxx` and `ValidXxx` +- Test edge cases and boundary conditions + +## Validation Framework Components + +### Available Case Methods (from TypesSwitch) +- `caseObjectType(type: ObjectType)` - for validating object types +- `caseProperty(property: Property)` - for validating properties +- `caseStringType(type: StringType)` - for string types +- `caseBooleanType(type: BooleanType)` - for boolean types +- `caseArrayType(type: ArrayType)` - for array types +- And many more from the EMF TypesSwitch hierarchy + +### Helper Methods +- `error(object, message, args...)` - creates an ERROR diagnostic +- `create(object, message, args...)` - creates a diagnostic with the rule's severity +- Type checking helpers can be created as private extension methods + +### Common Patterns + +**Filtering with exclusions:** +```kotlin +if (exclude.contains(property.name).not()) { + // validation logic +} +``` + +**Pattern matching:** +```kotlin +if (property.name.matches(Regex("^is[A-Z].*$"))) { + validationResults.add(error(type, "Error message", property.name)) +} +``` + +**Type checking:** +```kotlin +private fun AnyType.isBoolean(): Boolean { + return when(this) { + is BooleanType -> true + else -> false + } +} +``` + +## Workflow for Creating a New Rule + +1. **Understand the requirement**: Clarify what the rule should validate +2. **Create the rule file**: `YourRuleNameRule.kt` in the validators directory +3. **Create the test RAML**: `your-rule-name.raml` in test resources +4. **Add the test case**: Add test method to `ValidatorRulesTest.groovy` +5. **Run tests**: Use `./gradlew :ctp-validators:test` to verify +6. **Document**: Ensure error messages are clear and helpful + +## Example Reference + +See `BooleanPropertyNameRule.kt` for a complete example that: +- Validates boolean property names don't have "is" prefix +- Supports exclusions +- Has comprehensive test coverage +- Includes clear error messages + +## Important Notes + +- All rule files must have the `@ValidatorSet` annotation +- Rule classes must extend `TypesRule` or another appropriate base validator +- Companion objects must implement `ValidatorFactory` +- Test RAML files should be comprehensive with both positive and negative cases +- Error messages should be descriptive and include context (property name, type name, etc.) +- Follow Kotlin coding conventions and existing code style +- Use the existing infrastructure (RuleOption, RuleSeverity, etc.) + +When creating a new rule, you should: +1. Ask clarifying questions about the validation requirements +2. Create the rule class with proper structure +3. Create comprehensive test RAML with edge cases +4. Add the test case to ValidatorRulesTest.groovy +5. Verify all three files work together correctly From 51ed5bbe144823c016f4d75f6d00e5a2d2dd6f0e Mon Sep 17 00:00:00 2001 From: FFawzy Date: Tue, 17 Feb 2026 21:12:28 +0100 Subject: [PATCH 14/45] test: adding a new EnumValuePascalCaseRule --- .../rmf/validators/EnumValuePascalCaseRule.kt | 66 +++++++++++++++ .../rmf/validators/ValidatorRulesTest.groovy | 36 +++++++++ .../enum-value-pascal-case-rule.raml | 81 +++++++++++++++++++ 3 files changed, 183 insertions(+) create mode 100644 ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/EnumValuePascalCaseRule.kt create mode 100644 ctp-validators/src/test/resources/enum-value-pascal-case-rule.raml diff --git a/ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/EnumValuePascalCaseRule.kt b/ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/EnumValuePascalCaseRule.kt new file mode 100644 index 00000000..ce3bf2a3 --- /dev/null +++ b/ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/EnumValuePascalCaseRule.kt @@ -0,0 +1,66 @@ +package com.commercetools.rmf.validators + +import io.vrap.rmf.raml.model.types.StringType +import org.eclipse.emf.common.util.Diagnostic +import java.util.* + +@ValidatorSet +class EnumValuePascalCaseRule(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 caseStringType(type: StringType): List { + val validationResults: MutableList = ArrayList() + + if (exclude.contains(type.name).not() && type.name != "string" && type.enum.isNullOrEmpty().not()) { + type.enum.forEach { enumValue -> + val enumName = enumValue.value as? String + if (enumName != null && !isPascalCase(enumName)) { + validationResults.add( + error( + type, + "Enum value \"{0}\" in type \"{1}\" must be PascalCase", + enumName, + type.name + ) + ) + } + } + } + + return validationResults + } + + private fun isPascalCase(value: String): Boolean { + if (value.isEmpty()) return false + + // Must start with uppercase letter + if (!value[0].isUpperCase()) return false + + // Should not contain underscores or hyphens (common in snake_case or kebab-case) + if (value.contains('_') || value.contains('-')) return false + + // Should not be all uppercase (SCREAMING_SNAKE_CASE) + if (value.length > 1 && value.all { it.isUpperCase() || !it.isLetter() }) return false + + // Check that it only contains letters (PascalCase should not have numbers at the start or special characters) + // Allow letters and numbers, but must start with uppercase letter + return value.all { it.isLetterOrDigit() } + } + + companion object : ValidatorFactory { + private val defaultExcludes by lazy { listOf("") } + + @JvmStatic + override fun create(options: List?): EnumValuePascalCaseRule { + return EnumValuePascalCaseRule(RuleSeverity.ERROR, options) + } + + @JvmStatic + override fun create(severity: RuleSeverity, options: List?): EnumValuePascalCaseRule { + return EnumValuePascalCaseRule(severity, options) + } + } +} 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 8f25e415..6dc65aed 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 @@ -428,4 +428,40 @@ class ValidatorRulesTest extends Specification implements ValidatorFixtures { result.validationResults[0].message == "Type \"InvalidBaz\" has subtypes but no discriminator is set" } + def "enum value pascal case rule"() { + when: + def validators = Arrays.asList(new TypesValidator(Arrays.asList(EnumValuePascalCaseRule.create(emptyList())))) + def uri = uriFromClasspath("/enum-value-pascal-case-rule.raml") + def result = new RamlModelBuilder(validators).buildApi(uri) + then: + result.validationResults.size() == 17 + result.validationResults[0].message == "Enum value \"platform\" in type \"InvalidLowercaseEnum\" must be PascalCase" + result.validationResults[1].message == "Enum value \"external\" in type \"InvalidLowercaseEnum\" must be PascalCase" + result.validationResults[2].message == "Enum value \"disabled\" in type \"InvalidLowercaseEnum\" must be PascalCase" + result.validationResults[3].message == "Enum value \"PLATFORM\" in type \"InvalidUppercaseEnum\" must be PascalCase" + result.validationResults[4].message == "Enum value \"EXTERNAL\" in type \"InvalidUppercaseEnum\" must be PascalCase" + result.validationResults[5].message == "Enum value \"DISABLED\" in type \"InvalidUppercaseEnum\" must be PascalCase" + result.validationResults[6].message == "Enum value \"add_line_item\" in type \"InvalidSnakeCaseEnum\" must be PascalCase" + result.validationResults[7].message == "Enum value \"remove_line_item\" in type \"InvalidSnakeCaseEnum\" must be PascalCase" + result.validationResults[8].message == "Enum value \"set_custom_type\" in type \"InvalidSnakeCaseEnum\" must be PascalCase" + result.validationResults[9].message == "Enum value \"addLineItem\" in type \"InvalidCamelCaseEnum\" must be PascalCase" + result.validationResults[10].message == "Enum value \"removeLineItem\" in type \"InvalidCamelCaseEnum\" must be PascalCase" + result.validationResults[11].message == "Enum value \"setCustomType\" in type \"InvalidCamelCaseEnum\" must be PascalCase" + result.validationResults[12].message == "Enum value \"add-line-item\" in type \"InvalidKebabCaseEnum\" must be PascalCase" + result.validationResults[13].message == "Enum value \"remove-line-item\" in type \"InvalidKebabCaseEnum\" must be PascalCase" + result.validationResults[14].message == "Enum value \"invalidCamelCase\" in type \"MixedCaseEnum\" must be PascalCase" + result.validationResults[15].message == "Enum value \"INVALID_UPPER\" in type \"MixedCaseEnum\" must be PascalCase" + result.validationResults[16].message == "Enum value \"another-invalid\" in type \"MixedCaseEnum\" must be PascalCase" + } + + def "enum value pascal case rule with exclusions"() { + when: + def options = singletonList(new RuleOption(RuleOptionType.EXCLUDE.toString(), "InvalidLowercaseEnum")) + def validators = Arrays.asList(new TypesValidator(Arrays.asList(EnumValuePascalCaseRule.create(options)))) + def uri = uriFromClasspath("/enum-value-pascal-case-rule.raml") + def result = new RamlModelBuilder(validators).buildApi(uri) + then: + result.validationResults.size() == 14 + } + } diff --git a/ctp-validators/src/test/resources/enum-value-pascal-case-rule.raml b/ctp-validators/src/test/resources/enum-value-pascal-case-rule.raml new file mode 100644 index 00000000..1a03f32a --- /dev/null +++ b/ctp-validators/src/test/resources/enum-value-pascal-case-rule.raml @@ -0,0 +1,81 @@ +#%RAML 1.0 +title: enum value pascal case rule + +annotationTypes: + package: string + +types: + # Valid enum - all values are PascalCase + ValidStatusEnum: + (package): Common + type: string + enum: + - Platform + - External + - Disabled + - Active + - Ordered + + # Valid enum - PascalCase with multiple words + ValidActionTypeEnum: + (package): Common + type: string + enum: + - AddLineItem + - RemoveLineItem + - ChangeQuantity + - SetCustomType + + # Invalid enum - lowercase values + InvalidLowercaseEnum: + (package): Common + type: string + enum: + - platform + - external + - disabled + + # Invalid enum - UPPERCASE (SCREAMING_SNAKE_CASE style) + InvalidUppercaseEnum: + (package): Common + type: string + enum: + - PLATFORM + - EXTERNAL + - DISABLED + + # Invalid enum - snake_case values + InvalidSnakeCaseEnum: + (package): Common + type: string + enum: + - add_line_item + - remove_line_item + - set_custom_type + + # Invalid enum - camelCase values (starts with lowercase) + InvalidCamelCaseEnum: + (package): Common + type: string + enum: + - addLineItem + - removeLineItem + - setCustomType + + # Invalid enum - kebab-case values + InvalidKebabCaseEnum: + (package): Common + type: string + enum: + - add-line-item + - remove-line-item + + # Mixed valid and invalid + MixedCaseEnum: + (package): Common + type: string + enum: + - ValidPascalCase + - invalidCamelCase + - INVALID_UPPER + - another-invalid From 7096bda51e74c687fd80cbec27831d8118d2ef20 Mon Sep 17 00:00:00 2001 From: FFawzy Date: Wed, 18 Feb 2026 17:45:46 +0100 Subject: [PATCH 15/45] update agent with documentation step --- .github/agents/ctp-validator-rule-creator.agent.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/agents/ctp-validator-rule-creator.agent.md b/.github/agents/ctp-validator-rule-creator.agent.md index e0fac653..24cfd52f 100644 --- a/.github/agents/ctp-validator-rule-creator.agent.md +++ b/.github/agents/ctp-validator-rule-creator.agent.md @@ -192,11 +192,11 @@ private fun AnyType.isBoolean(): Boolean { ## Workflow for Creating a New Rule 1. **Understand the requirement**: Clarify what the rule should validate -2. **Create the rule file**: `YourRuleNameRule.kt` in the validators directory -3. **Create the test RAML**: `your-rule-name.raml` in test resources +2. **Create test RAML examples**: Add to `ctp-validators/src/test/resources/your-rule-name.raml` with both valid and invalid test cases +3. **Create the rule file**: `YourRuleNameRule.kt` in the validators directory with appropriate `case` methods 4. **Add the test case**: Add test method to `ValidatorRulesTest.groovy` 5. **Run tests**: Use `./gradlew :ctp-validators:test` to verify -6. **Document**: Ensure error messages are clear and helpful +6. **Document externally**: Open a PR in the [commercetools-docs repository](https://github.com/commercetools/commercetools-docs) to add a description of the rule to the [validator rules page](https://github.com/commercetools/commercetools-docs/tree/main/api-specs#validator-rules) ## Example Reference From df28ec95bd3a6e32d7994f2631a8808e43b72621 Mon Sep 17 00:00:00 2001 From: Jens Schulze Date: Fri, 20 Feb 2026 09:22:49 +0100 Subject: [PATCH 16/45] optimize enum value rule --- .../rmf/validators/EnumValuePascalCaseRule.kt | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/EnumValuePascalCaseRule.kt b/ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/EnumValuePascalCaseRule.kt index ce3bf2a3..689413aa 100644 --- a/ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/EnumValuePascalCaseRule.kt +++ b/ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/EnumValuePascalCaseRule.kt @@ -1,6 +1,7 @@ package com.commercetools.rmf.validators import io.vrap.rmf.raml.model.types.StringType +import io.vrap.rmf.raml.model.util.StringCaseFormat import org.eclipse.emf.common.util.Diagnostic import java.util.* @@ -34,20 +35,7 @@ class EnumValuePascalCaseRule(severity: RuleSeverity, options: List? } private fun isPascalCase(value: String): Boolean { - if (value.isEmpty()) return false - - // Must start with uppercase letter - if (!value[0].isUpperCase()) return false - - // Should not contain underscores or hyphens (common in snake_case or kebab-case) - if (value.contains('_') || value.contains('-')) return false - - // Should not be all uppercase (SCREAMING_SNAKE_CASE) - if (value.length > 1 && value.all { it.isUpperCase() || !it.isLetter() }) return false - - // Check that it only contains letters (PascalCase should not have numbers at the start or special characters) - // Allow letters and numbers, but must start with uppercase letter - return value.all { it.isLetterOrDigit() } + return value == StringCaseFormat.UPPER_CAMEL_CASE.apply(value) } companion object : ValidatorFactory { From 4ac272112a2ce6c27fd276d75fb44f9ddb05cb5d Mon Sep 17 00:00:00 2001 From: Thorsten Gast Date: Tue, 3 Feb 2026 14:30:54 +0100 Subject: [PATCH 17/45] Remove [ and ] in exportNames to allow proper generation of deep-query parameters --- .../kotlin/io/vrap/codegen/languages/go/GoStringExtension.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/languages/go/src/main/kotlin/io/vrap/codegen/languages/go/GoStringExtension.kt b/languages/go/src/main/kotlin/io/vrap/codegen/languages/go/GoStringExtension.kt index 180515f6..3bc07a34 100644 --- a/languages/go/src/main/kotlin/io/vrap/codegen/languages/go/GoStringExtension.kt +++ b/languages/go/src/main/kotlin/io/vrap/codegen/languages/go/GoStringExtension.kt @@ -41,7 +41,7 @@ fun String.exportName(): String { if (this[0].isUpperCase()) { return this } - var name = StringCaseFormat.UPPER_CAMEL_CASE.apply(this.replace(".", "_")) + var name = StringCaseFormat.UPPER_CAMEL_CASE.apply(this.replace(Regex("[.\\[\\]]"), "_")) mapOf( "^Id$" to "ID" ).forEach { (key, value) -> name = name.replace(key.toRegex(), value) } From bc7aba621ebc43c2c6024a0dbf59e67fb6a04eb8 Mon Sep 17 00:00:00 2001 From: Jens Schulze Date: Fri, 20 Feb 2026 11:05:19 +0100 Subject: [PATCH 18/45] update publishing --- .github/workflows/release.yml | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7131f69d..0eb8e6b2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -42,21 +42,36 @@ jobs: SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Setup Node + + # Configure npm for OIDC authentication with trusted publishing + # This must be done after CI setup to ensure npm is properly configured + + # setup-node@v4 with registry-url automatically configures OIDC when id-token: write is set + - name: Setup Node.js for npm publishing uses: actions/setup-node@v4 with: - node-version: "18" + node-version: "24" + registry-url: "https://registry.npmjs.org" - name: Build NPM package run: | ./gradlew -Pversion=${{ github.ref_name }} tools:cli-application:shadowJar cp rmf-codegen.jar node/rmf-codegen/bin + - name: Verify npm OIDC configuration + run: | + # Verify registry is set correctly + echo "Registry: $(npm config get registry)" + # Ensure no token-based auth is configured (OIDC should be used automatically) + npm config delete //registry.npmjs.org/:_authToken || true + # Verify npm can access the registry (this will use OIDC if configured) + echo "npm OIDC authentication configured via setup-node action" + - name: Creating .npmrc run: | cat << EOF > "$HOME/.npmrc" email=npmjs@commercetools.com - //registry.npmjs.org/:_authToken=$NPM_TOKEN +# //registry.npmjs.org/:_authToken=$NPM_TOKEN EOF env: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} From c8175b7f60607e22eb07f66b54835115ca9d7f31 Mon Sep 17 00:00:00 2001 From: Jens Schulze Date: Fri, 20 Feb 2026 11:08:45 +0100 Subject: [PATCH 19/45] Remove commented NPM auth token line from release.yml Remove commented-out line for NPM authentication token in .npmrc creation step. --- .github/workflows/release.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0eb8e6b2..6ca521f3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -71,7 +71,6 @@ jobs: run: | cat << EOF > "$HOME/.npmrc" email=npmjs@commercetools.com -# //registry.npmjs.org/:_authToken=$NPM_TOKEN EOF env: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} From e9c85e664ec83f47d469ea5472820809e5e7b655 Mon Sep 17 00:00:00 2001 From: Jens Schulze Date: Mon, 23 Feb 2026 15:27:27 +0100 Subject: [PATCH 20/45] add token to npmrc --- .github/workflows/release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6ca521f3..77e6eeaa 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -71,6 +71,7 @@ jobs: run: | cat << EOF > "$HOME/.npmrc" email=npmjs@commercetools.com + //registry.npmjs.org/:_authToken=$NPM_TOKEN EOF env: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} From 79b586fdd272f058d6e06c3ab50c6ea63d4d1574 Mon Sep 17 00:00:00 2001 From: Jens Schulze Date: Mon, 23 Feb 2026 18:29:35 +0100 Subject: [PATCH 21/45] try release without token --- .github/workflows/release.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 77e6eeaa..b9e03f91 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -36,7 +36,7 @@ jobs: GRADLE_PUBLISH_SECRET: ${{ secrets.GRADLE_PUBLISH_SECRET }} - name: Build and Release - run: ./gradlew -Pversion=${{ github.ref_name }} clean check publishMavenPublicationToSonatype closeAndReleaseSonatypeStagingRepository + run: ./gradlew -Pversion=${{ github.ref_name }} clean check # publishMavenPublicationToSonatype closeAndReleaseSonatypeStagingRepository env: SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} @@ -67,14 +67,14 @@ jobs: # Verify npm can access the registry (this will use OIDC if configured) echo "npm OIDC authentication configured via setup-node action" - - name: Creating .npmrc - run: | - cat << EOF > "$HOME/.npmrc" - email=npmjs@commercetools.com - //registry.npmjs.org/:_authToken=$NPM_TOKEN - EOF - env: - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} +# - name: Creating .npmrc +# run: | +# cat << EOF > "$HOME/.npmrc" +# email=npmjs@commercetools.com +# //registry.npmjs.org/:_authToken=$NPM_TOKEN +# EOF +# env: +# NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - name: Publish npm package working-directory: node/rmf-codegen From b279fd2cab07a388cc5cb308b18ac911ea5847fd Mon Sep 17 00:00:00 2001 From: Jens Schulze Date: Tue, 24 Feb 2026 11:44:57 +0100 Subject: [PATCH 22/45] id-token global --- .github/workflows/release.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b9e03f91..eddf43e1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,8 +5,9 @@ on: name: AutoRelease -permissions: {} - +permissions: + id-token: write + contents: read jobs: release_maven: permissions: From 137a951cb312283af4a0cd4777008d4a4fc90a7d Mon Sep 17 00:00:00 2001 From: Jens Schulze Date: Tue, 24 Feb 2026 11:55:48 +0100 Subject: [PATCH 23/45] use npm publish --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index eddf43e1..7091759c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -79,7 +79,7 @@ jobs: - name: Publish npm package working-directory: node/rmf-codegen - run: yarn publish --no-git-tag-version --minor + run: yarn npm publish --no-git-tag-version --minor bump_version: name: Bump NPM version From cdde1c141a27f167a1567a13756b05241ec83b3e Mon Sep 17 00:00:00 2001 From: Jens Schulze Date: Tue, 24 Feb 2026 11:56:16 +0100 Subject: [PATCH 24/45] id token only in job available --- .github/workflows/release.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7091759c..dad8bc83 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,9 +5,8 @@ on: name: AutoRelease -permissions: - id-token: write - contents: read +permissions: {} + jobs: release_maven: permissions: From 129812ea271693d47d6091f317886d9679b1312d Mon Sep 17 00:00:00 2001 From: Jens Schulze Date: Tue, 24 Feb 2026 12:02:15 +0100 Subject: [PATCH 25/45] use npm for publish --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dad8bc83..ee387494 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -78,7 +78,7 @@ jobs: - name: Publish npm package working-directory: node/rmf-codegen - run: yarn npm publish --no-git-tag-version --minor + run: npm publish --no-git-tag-version --minor bump_version: name: Bump NPM version From 63cfeea4f0ad3da62bed75689b31eac9afb8017e Mon Sep 17 00:00:00 2001 From: Jens Schulze Date: Tue, 24 Feb 2026 12:10:27 +0100 Subject: [PATCH 26/45] increase version with npm --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ee387494..3ef0dbb1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -78,7 +78,7 @@ jobs: - name: Publish npm package working-directory: node/rmf-codegen - run: npm publish --no-git-tag-version --minor + run: npm version minor && npm publish --no-git-tag-version bump_version: name: Bump NPM version From 1682a7830a7c95b5cef86b1367929bc3f24aa127 Mon Sep 17 00:00:00 2001 From: Jens Schulze Date: Tue, 24 Feb 2026 12:20:10 +0100 Subject: [PATCH 27/45] re-enable publish to maven --- .github/workflows/release.yml | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3ef0dbb1..cfb6c79d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -36,13 +36,12 @@ jobs: GRADLE_PUBLISH_SECRET: ${{ secrets.GRADLE_PUBLISH_SECRET }} - name: Build and Release - run: ./gradlew -Pversion=${{ github.ref_name }} clean check # publishMavenPublicationToSonatype closeAndReleaseSonatypeStagingRepository + run: ./gradlew -Pversion=${{ github.ref_name }} clean check publishMavenPublicationToSonatype closeAndReleaseSonatypeStagingRepository env: SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # Configure npm for OIDC authentication with trusted publishing # This must be done after CI setup to ensure npm is properly configured @@ -67,15 +66,6 @@ jobs: # Verify npm can access the registry (this will use OIDC if configured) echo "npm OIDC authentication configured via setup-node action" -# - name: Creating .npmrc -# run: | -# cat << EOF > "$HOME/.npmrc" -# email=npmjs@commercetools.com -# //registry.npmjs.org/:_authToken=$NPM_TOKEN -# EOF -# env: -# NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - - name: Publish npm package working-directory: node/rmf-codegen run: npm version minor && npm publish --no-git-tag-version From c60520352ea5eb0ac5fa3184b6bf735d64956928 Mon Sep 17 00:00:00 2001 From: Auto Mation Date: Tue, 24 Feb 2026 11:14:49 +0000 Subject: [PATCH 28/45] Bump codegen version --- node/rmf-codegen/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/rmf-codegen/package.json b/node/rmf-codegen/package.json index c9f5f412..d0c44d31 100644 --- a/node/rmf-codegen/package.json +++ b/node/rmf-codegen/package.json @@ -1,6 +1,6 @@ { "name": "@commercetools-docs/rmf-codegen", - "version": "13.48.0", + "version": "13.49.0", "description": "Provides RMF-Codegen to javascript projects", "license": "MIT", "homepage": "https://github.com/commercetools/rmf-codegen", From 3c1ff8d8f86a78d88c03bd7c1937c31c05f32619 Mon Sep 17 00:00:00 2001 From: Auto Mation Date: Tue, 24 Feb 2026 11:46:15 +0000 Subject: [PATCH 29/45] TASK: Updating version in README --- scripts/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/install.sh b/scripts/install.sh index a36f1cfb..48d4d3ad 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -2,7 +2,7 @@ set -e -CODEGEN_VERSION=${VRAP_VERSION:-"1.0.0-20251219114602"} +CODEGEN_VERSION=${VRAP_VERSION:-"1.0.0-20260224113723"} CLI_HOME=~/.rmf-cli LIB_FOLDER=$CLI_HOME/lib JAR_FILE_PATH=$LIB_FOLDER/codegen-cli-${CODEGEN_VERSION}.jar From 893324768843d3b83118f4a7e1e0b4540123933a Mon Sep 17 00:00:00 2001 From: Jens Schulze Date: Tue, 24 Feb 2026 14:05:36 +0100 Subject: [PATCH 30/45] adjust enumpascal case exclusion to include type and enum value --- .../commercetools/rmf/validators/EnumValuePascalCaseRule.kt | 4 ++-- .../commercetools/rmf/validators/ValidatorRulesTest.groovy | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/EnumValuePascalCaseRule.kt b/ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/EnumValuePascalCaseRule.kt index 689413aa..c1a7846c 100644 --- a/ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/EnumValuePascalCaseRule.kt +++ b/ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/EnumValuePascalCaseRule.kt @@ -15,10 +15,10 @@ class EnumValuePascalCaseRule(severity: RuleSeverity, options: List? override fun caseStringType(type: StringType): List { val validationResults: MutableList = ArrayList() - if (exclude.contains(type.name).not() && type.name != "string" && type.enum.isNullOrEmpty().not()) { + if (type.name != "string" && type.enum.isNullOrEmpty().not()) { type.enum.forEach { enumValue -> val enumName = enumValue.value as? String - if (enumName != null && !isPascalCase(enumName)) { + if (enumName != null && exclude.contains("${type.name}:${enumName}").not() && !isPascalCase(enumName)) { validationResults.add( error( type, 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 6dc65aed..ccf92883 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 @@ -456,12 +456,12 @@ class ValidatorRulesTest extends Specification implements ValidatorFixtures { def "enum value pascal case rule with exclusions"() { when: - def options = singletonList(new RuleOption(RuleOptionType.EXCLUDE.toString(), "InvalidLowercaseEnum")) + def options = singletonList(new RuleOption(RuleOptionType.EXCLUDE.toString(), "InvalidLowercaseEnum:platform")) def validators = Arrays.asList(new TypesValidator(Arrays.asList(EnumValuePascalCaseRule.create(options)))) def uri = uriFromClasspath("/enum-value-pascal-case-rule.raml") def result = new RamlModelBuilder(validators).buildApi(uri) then: - result.validationResults.size() == 14 + result.validationResults.size() == 16 } } From 648b70dbf99d1300858dc126770f92e0351f9635 Mon Sep 17 00:00:00 2001 From: Auto Mation Date: Tue, 24 Feb 2026 13:19:23 +0000 Subject: [PATCH 31/45] Bump codegen version --- node/rmf-codegen/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/rmf-codegen/package.json b/node/rmf-codegen/package.json index d0c44d31..6a906365 100644 --- a/node/rmf-codegen/package.json +++ b/node/rmf-codegen/package.json @@ -1,6 +1,6 @@ { "name": "@commercetools-docs/rmf-codegen", - "version": "13.49.0", + "version": "13.50.0", "description": "Provides RMF-Codegen to javascript projects", "license": "MIT", "homepage": "https://github.com/commercetools/rmf-codegen", From 9e41d54ce497851dc06b3656643288c4fcaa8fc3 Mon Sep 17 00:00:00 2001 From: Jens Schulze Date: Thu, 26 Feb 2026 14:39:26 +0100 Subject: [PATCH 32/45] support enumvaluepascalcaserule exclusion by type name only --- .../rmf/validators/EnumValuePascalCaseRule.kt | 2 +- .../rmf/validators/ValidatorRulesTest.groovy | 16 +++++++++++++--- .../resources/enum-value-pascal-case-rule.raml | 6 ++++++ 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/EnumValuePascalCaseRule.kt b/ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/EnumValuePascalCaseRule.kt index c1a7846c..61920b6e 100644 --- a/ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/EnumValuePascalCaseRule.kt +++ b/ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/EnumValuePascalCaseRule.kt @@ -18,7 +18,7 @@ class EnumValuePascalCaseRule(severity: RuleSeverity, options: List? if (type.name != "string" && type.enum.isNullOrEmpty().not()) { type.enum.forEach { enumValue -> val enumName = enumValue.value as? String - if (enumName != null && exclude.contains("${type.name}:${enumName}").not() && !isPascalCase(enumName)) { + if (enumName != null && exclude.contains("${type.name}:${enumName}").not() && exclude.contains("${type.name}").not() && !isPascalCase(enumName)) { validationResults.add( error( type, 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 ccf92883..70ac193b 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 @@ -434,7 +434,7 @@ class ValidatorRulesTest extends Specification implements ValidatorFixtures { def uri = uriFromClasspath("/enum-value-pascal-case-rule.raml") def result = new RamlModelBuilder(validators).buildApi(uri) then: - result.validationResults.size() == 17 + result.validationResults.size() == 19 result.validationResults[0].message == "Enum value \"platform\" in type \"InvalidLowercaseEnum\" must be PascalCase" result.validationResults[1].message == "Enum value \"external\" in type \"InvalidLowercaseEnum\" must be PascalCase" result.validationResults[2].message == "Enum value \"disabled\" in type \"InvalidLowercaseEnum\" must be PascalCase" @@ -452,6 +452,8 @@ class ValidatorRulesTest extends Specification implements ValidatorFixtures { result.validationResults[14].message == "Enum value \"invalidCamelCase\" in type \"MixedCaseEnum\" must be PascalCase" result.validationResults[15].message == "Enum value \"INVALID_UPPER\" in type \"MixedCaseEnum\" must be PascalCase" result.validationResults[16].message == "Enum value \"another-invalid\" in type \"MixedCaseEnum\" must be PascalCase" + result.validationResults[17].message == "Enum value \"centPrecision\" in type \"MoneyType\" must be PascalCase" + result.validationResults[18].message == "Enum value \"highPrecision\" in type \"MoneyType\" must be PascalCase" } def "enum value pascal case rule with exclusions"() { @@ -461,7 +463,15 @@ class ValidatorRulesTest extends Specification implements ValidatorFixtures { def uri = uriFromClasspath("/enum-value-pascal-case-rule.raml") def result = new RamlModelBuilder(validators).buildApi(uri) then: - result.validationResults.size() == 16 + result.validationResults.size() == 18 + } + def "enum value pascal case rule with type exclusion"() { + when: + def options = singletonList(new RuleOption(RuleOptionType.EXCLUDE.toString(), "MoneyType")) + def validators = Arrays.asList(new TypesValidator(Arrays.asList(EnumValuePascalCaseRule.create(options)))) + def uri = uriFromClasspath("/enum-value-pascal-case-rule.raml") + def result = new RamlModelBuilder(validators).buildApi(uri) + then: + result.validationResults.size() == 17 } - } diff --git a/ctp-validators/src/test/resources/enum-value-pascal-case-rule.raml b/ctp-validators/src/test/resources/enum-value-pascal-case-rule.raml index 1a03f32a..3535e895 100644 --- a/ctp-validators/src/test/resources/enum-value-pascal-case-rule.raml +++ b/ctp-validators/src/test/resources/enum-value-pascal-case-rule.raml @@ -79,3 +79,9 @@ types: - invalidCamelCase - INVALID_UPPER - another-invalid + MoneyType: + (package): Common + type: string + enum: + - centPrecision + - highPrecision From 81ac6e6c289875964555dfd2aab569482ec1f9bb Mon Sep 17 00:00:00 2001 From: Auto Mation Date: Thu, 26 Feb 2026 13:50:10 +0000 Subject: [PATCH 33/45] TASK: Updating version in README --- scripts/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/install.sh b/scripts/install.sh index 48d4d3ad..dea7c701 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -2,7 +2,7 @@ set -e -CODEGEN_VERSION=${VRAP_VERSION:-"1.0.0-20260224113723"} +CODEGEN_VERSION=${VRAP_VERSION:-"1.0.0-20260226134024"} CLI_HOME=~/.rmf-cli LIB_FOLDER=$CLI_HOME/lib JAR_FILE_PATH=$LIB_FOLDER/codegen-cli-${CODEGEN_VERSION}.jar From b9cb2f6693e45aaea5bde9a4d1e26504c7818f60 Mon Sep 17 00:00:00 2001 From: Auto Mation Date: Thu, 26 Feb 2026 13:50:24 +0000 Subject: [PATCH 34/45] Bump codegen version --- node/rmf-codegen/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/rmf-codegen/package.json b/node/rmf-codegen/package.json index 6a906365..af44ad3c 100644 --- a/node/rmf-codegen/package.json +++ b/node/rmf-codegen/package.json @@ -1,6 +1,6 @@ { "name": "@commercetools-docs/rmf-codegen", - "version": "13.50.0", + "version": "13.51.0", "description": "Provides RMF-Codegen to javascript projects", "license": "MIT", "homepage": "https://github.com/commercetools/rmf-codegen", From c2015abb99fc5b10db83ab68d5ac96d4b5538aba Mon Sep 17 00:00:00 2001 From: Jens Schulze Date: Thu, 26 Feb 2026 15:00:30 +0100 Subject: [PATCH 35/45] unify release skript --- .github/workflows/release.yml | 54 +++++++++++++++++++++++++++-- .github/workflows/release_maven.yml | 54 ----------------------------- 2 files changed, 52 insertions(+), 56 deletions(-) delete mode 100644 .github/workflows/release_maven.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cfb6c79d..fcb9f228 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,4 +1,5 @@ on: + workflow_dispatch: push: tags: - '[0-9]+.[0-9]+.[0-9]+*' @@ -9,14 +10,62 @@ permissions: {} jobs: release_maven: + permissions: + contents: write + actions: write + id-token: write + + name: Build and release + + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '11' + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: "18" + + - run: scripts/setup-signing-key.sh + env: + DECRYPTER: ${{ secrets.DECRYPTER }} + SIGNING_KEY: ${{ secrets.SIGNING_KEY }} + PASSPHRASE: ${{ secrets.PASSPHRASE }} + GRADLE_PUBLISH_KEY: ${{ secrets.GRADLE_PUBLISH_KEY }} + GRADLE_PUBLISH_SECRET: ${{ secrets.GRADLE_PUBLISH_SECRET }} + - name: Build and Release + run: export VERSION="1.0.0-`date '+%Y%m%d%H%M%S'`"; ./gradlew -Pversion=$VERSION clean check publishMavenPublicationToSonatype closeAndReleaseSonatypeStagingRepository writeVersionToReadme + env: + SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} + SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - uses: stefanzweifel/git-auto-commit-action@v4.6.0 + with: + file_pattern: "scripts/install.sh" + commit_message: "TASK: Updating version in README" + commit_user_name: Auto Mation + commit_user_email: automation@commercetools.com + commit_author: Auto Mation + + release_tag: permissions: id-token: write contents: read name: Build and release to Maven + if: startsWith( github.ref, 'refs/tags/') runs-on: ubuntu-latest - + needs: [release_maven] steps: - name: Checkout uses: actions/checkout@v4 @@ -71,9 +120,10 @@ jobs: run: npm version minor && npm publish --no-git-tag-version bump_version: + if: startsWith( github.ref, 'refs/tags/') name: Bump NPM version - needs: [release_maven] + needs: [release_tag] runs-on: ubuntu-latest permissions: diff --git a/.github/workflows/release_maven.yml b/.github/workflows/release_maven.yml deleted file mode 100644 index 602a5ed0..00000000 --- a/.github/workflows/release_maven.yml +++ /dev/null @@ -1,54 +0,0 @@ -on: - - workflow_dispatch - -name: Release to Maven Central - -permissions: {} - -jobs: - release: - permissions: - contents: write - actions: write - id-token: write - - name: Build and release - - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Java - uses: actions/setup-java@v4 - with: - distribution: 'temurin' - java-version: '11' - - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: "18" - - - run: scripts/setup-signing-key.sh - env: - DECRYPTER: ${{ secrets.DECRYPTER }} - SIGNING_KEY: ${{ secrets.SIGNING_KEY }} - PASSPHRASE: ${{ secrets.PASSPHRASE }} - GRADLE_PUBLISH_KEY: ${{ secrets.GRADLE_PUBLISH_KEY }} - GRADLE_PUBLISH_SECRET: ${{ secrets.GRADLE_PUBLISH_SECRET }} - - name: Build and Release - run: export VERSION="1.0.0-`date '+%Y%m%d%H%M%S'`"; ./gradlew -Pversion=$VERSION clean check publishMavenPublicationToSonatype closeAndReleaseSonatypeStagingRepository writeVersionToReadme - env: - SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} - SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - uses: stefanzweifel/git-auto-commit-action@v4.6.0 - with: - file_pattern: "scripts/install.sh" - commit_message: "TASK: Updating version in README" - commit_user_name: Auto Mation - commit_user_email: automation@commercetools.com - commit_author: Auto Mation From f05b3cde1b4c07a1b43c0138ce7950ad6bb5afac Mon Sep 17 00:00:00 2001 From: Mo Alras Date: Mon, 2 Mar 2026 09:35:04 +0100 Subject: [PATCH 36/45] Add PropertyMinMaxAbbreviationRule validation rule MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Validates that property names use abbreviated `min`/`max` instead of `minimum`/`maximum`, both as prefixes (e.g. `minimumQuantity` → `minQuantity`) and standalone (e.g. `minimum` → `min`). Supports TypeName:propertyName exclusions for backward-compatible exceptions. Co-Authored-By: Claude Opus 4.6 --- .../PropertyMinMaxAbbreviationRule.kt | 39 +++++++++++++ .../rmf/validators/ValidatorRulesTest.groovy | 25 +++++++++ .../property-minmax-abbreviation-rule.raml | 55 +++++++++++++++++++ 3 files changed, 119 insertions(+) create mode 100644 ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/PropertyMinMaxAbbreviationRule.kt create mode 100644 ctp-validators/src/test/resources/property-minmax-abbreviation-rule.raml diff --git a/ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/PropertyMinMaxAbbreviationRule.kt b/ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/PropertyMinMaxAbbreviationRule.kt new file mode 100644 index 00000000..43538fc9 --- /dev/null +++ b/ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/PropertyMinMaxAbbreviationRule.kt @@ -0,0 +1,39 @@ +package com.commercetools.rmf.validators + +import io.vrap.rmf.raml.model.types.* +import org.eclipse.emf.common.util.Diagnostic +import java.util.* + +@ValidatorSet +class PropertyMinMaxAbbreviationRule(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 caseObjectType(type: ObjectType): List { + val validationResults: MutableList = ArrayList() + + type.properties.forEach { property -> + if (exclude.contains("${type.name}:${property.name}").not()) { + if (property.name.matches(Regex("^(minimum|maximum)([A-Z].*)?$"))) { + validationResults.add(create(type, "Property \"{0}\" of type \"{1}\" must use \"min\"/\"max\" instead of \"minimum\"/\"maximum\"", property.name, type.name)) + } + } + } + return validationResults + } + + companion object : ValidatorFactory { + private val defaultExcludes by lazy { listOf("") } + + @JvmStatic + override fun create(options: List?): PropertyMinMaxAbbreviationRule { + return PropertyMinMaxAbbreviationRule(RuleSeverity.ERROR, options) + } + + @JvmStatic + override fun create(severity: RuleSeverity, options: List?): PropertyMinMaxAbbreviationRule { + return PropertyMinMaxAbbreviationRule(severity, options) + } + } +} 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 70ac193b..0755cacb 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 @@ -474,4 +474,29 @@ class ValidatorRulesTest extends Specification implements ValidatorFixtures { then: result.validationResults.size() == 17 } + + def "property min max abbreviation rule"() { + when: + def options = singletonList(new RuleOption(RuleOptionType.EXCLUDE.toString(), "InvalidMinMax:minimumExcluded")) + def validators = Arrays.asList(new TypesValidator(Arrays.asList(PropertyMinMaxAbbreviationRule.create(options)))) + def uri = uriFromClasspath("/property-minmax-abbreviation-rule.raml") + def result = new RamlModelBuilder(validators).buildApi(uri) + then: + result.validationResults.size() == 6 + result.validationResults[0].message == "Property \"minimumQuantity\" of type \"InvalidMinMax\" must use \"min\"/\"max\" instead of \"minimum\"/\"maximum\"" + result.validationResults[1].message == "Property \"maximumPrice\" of type \"InvalidMinMax\" must use \"min\"/\"max\" instead of \"minimum\"/\"maximum\"" + result.validationResults[2].message == "Property \"minimumOrder\" of type \"InvalidMinMax\" must use \"min\"/\"max\" instead of \"minimum\"/\"maximum\"" + result.validationResults[3].message == "Property \"maximumItems\" of type \"InvalidMinMax\" must use \"min\"/\"max\" instead of \"minimum\"/\"maximum\"" + result.validationResults[4].message == "Property \"minimum\" of type \"InvalidMinMax\" must use \"min\"/\"max\" instead of \"minimum\"/\"maximum\"" + result.validationResults[5].message == "Property \"maximum\" of type \"InvalidMinMax\" must use \"min\"/\"max\" instead of \"minimum\"/\"maximum\"" + } + + def "property min max abbreviation rule without exclusions"() { + when: + def validators = Arrays.asList(new TypesValidator(Arrays.asList(PropertyMinMaxAbbreviationRule.create(emptyList())))) + def uri = uriFromClasspath("/property-minmax-abbreviation-rule.raml") + def result = new RamlModelBuilder(validators).buildApi(uri) + then: + result.validationResults.size() == 7 + } } diff --git a/ctp-validators/src/test/resources/property-minmax-abbreviation-rule.raml b/ctp-validators/src/test/resources/property-minmax-abbreviation-rule.raml new file mode 100644 index 00000000..5df31a69 --- /dev/null +++ b/ctp-validators/src/test/resources/property-minmax-abbreviation-rule.raml @@ -0,0 +1,55 @@ +#%RAML 1.0 +title: property min max abbreviation rule + +annotationTypes: + package: string + sdkBaseUri: string + +baseUri: https://api.europe-west1.commercetools.com + +types: + InvalidMinMax: + (package): Common + type: object + properties: + minimumQuantity: + description: uses minimum as prefix - should be minQuantity + type: number + maximumPrice: + description: uses maximum as prefix - should be maxPrice + type: number + minimumOrder: + description: uses minimum as prefix - should be minOrder + type: number + maximumItems: + description: uses maximum as prefix - should be maxItems + type: number + minimum: + description: standalone minimum - should be min + type: number + maximum: + description: standalone maximum - should be max + type: number + minimumExcluded: + description: will be excluded via options + type: string + + ValidMinMax: + (package): Common + type: object + properties: + minQuantity: + description: correct abbreviated prefix + type: number + maxPrice: + description: correct abbreviated prefix + type: number + min: + description: correct standalone abbreviation + type: number + max: + description: correct standalone abbreviation + type: number + validName: + description: unrelated property name + type: string From a358ee9b4f5c543c8a660d482c55ffeb4c74cf56 Mon Sep 17 00:00:00 2001 From: Mo Alras Date: Mon, 2 Mar 2026 10:31:22 +0100 Subject: [PATCH 37/45] Add ParameterMinMaxAbbreviationRule for query params and headers Extends min/max abbreviation validation to query parameter and header names, complementing the existing PropertyMinMaxAbbreviationRule for type properties. Uses ResourcesRule with caseMethod() to check both method.queryParameters and method.headers. Co-Authored-By: Claude Opus 4.6 --- .../ParameterMinMaxAbbreviationRule.kt | 52 +++++++++++++++++++ .../rmf/validators/ValidatorRulesTest.groovy | 25 +++++++++ .../parameter-minmax-abbreviation-rule.raml | 17 ++++++ 3 files changed, 94 insertions(+) create mode 100644 ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/ParameterMinMaxAbbreviationRule.kt create mode 100644 ctp-validators/src/test/resources/parameter-minmax-abbreviation-rule.raml diff --git a/ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/ParameterMinMaxAbbreviationRule.kt b/ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/ParameterMinMaxAbbreviationRule.kt new file mode 100644 index 00000000..202d0afe --- /dev/null +++ b/ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/ParameterMinMaxAbbreviationRule.kt @@ -0,0 +1,52 @@ +package com.commercetools.rmf.validators + +import io.vrap.rmf.raml.model.resources.Method +import org.eclipse.emf.common.util.Diagnostic +import java.util.* + +@ValidatorSet +class ParameterMinMaxAbbreviationRule(severity: RuleSeverity, options: List? = null) : ResourcesRule(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 caseMethod(method: Method): List { + val validationResults: MutableList = ArrayList() + + method.queryParameters.forEach { queryParameter -> + run { + if (exclude.contains(queryParameter.name).not() && queryParameter.pattern == null) { + if (queryParameter.name.matches(Regex("^(minimum|maximum)([A-Z].*)?$"))) { + validationResults.add(create(queryParameter, "Query parameter \"{0}\" must use \"min\"/\"max\" instead of \"minimum\"/\"maximum\"", queryParameter.name)) + } + } + } + } + + method.headers.forEach { header -> + run { + if (exclude.contains(header.name).not()) { + if (header.name.matches(Regex("^(minimum|maximum)([A-Z].*)?$"))) { + validationResults.add(create(header, "Header \"{0}\" must use \"min\"/\"max\" instead of \"minimum\"/\"maximum\"", header.name)) + } + } + } + } + + return validationResults + } + + companion object : ValidatorFactory { + private val defaultExcludes by lazy { listOf("") } + + @JvmStatic + override fun create(options: List?): ParameterMinMaxAbbreviationRule { + return ParameterMinMaxAbbreviationRule(RuleSeverity.ERROR, options) + } + + @JvmStatic + override fun create(severity: RuleSeverity, options: List?): ParameterMinMaxAbbreviationRule { + return ParameterMinMaxAbbreviationRule(severity, options) + } + } +} 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 0755cacb..099205c7 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 @@ -499,4 +499,29 @@ class ValidatorRulesTest extends Specification implements ValidatorFixtures { then: result.validationResults.size() == 7 } + + def "parameter min max abbreviation rule"() { + when: + def options = singletonList(new RuleOption(RuleOptionType.EXCLUDE.toString(), "minimumExcluded")) + def validators = Arrays.asList(new ResourcesValidator(Arrays.asList(ParameterMinMaxAbbreviationRule.create(options)))) + def uri = uriFromClasspath("/parameter-minmax-abbreviation-rule.raml") + def result = new RamlModelBuilder(validators).buildApi(uri) + then: + result.validationResults.size() == 6 + result.validationResults[0].message == "Query parameter \"minimumQuantity\" must use \"min\"/\"max\" instead of \"minimum\"/\"maximum\"" + result.validationResults[1].message == "Query parameter \"maximumPrice\" must use \"min\"/\"max\" instead of \"minimum\"/\"maximum\"" + result.validationResults[2].message == "Query parameter \"minimum\" must use \"min\"/\"max\" instead of \"minimum\"/\"maximum\"" + result.validationResults[3].message == "Query parameter \"maximum\" must use \"min\"/\"max\" instead of \"minimum\"/\"maximum\"" + result.validationResults[4].message == "Header \"minimumRetry\" must use \"min\"/\"max\" instead of \"minimum\"/\"maximum\"" + result.validationResults[5].message == "Header \"maximumRetry\" must use \"min\"/\"max\" instead of \"minimum\"/\"maximum\"" + } + + def "parameter min max abbreviation rule without exclusions"() { + when: + def validators = Arrays.asList(new ResourcesValidator(Arrays.asList(ParameterMinMaxAbbreviationRule.create(emptyList())))) + def uri = uriFromClasspath("/parameter-minmax-abbreviation-rule.raml") + def result = new RamlModelBuilder(validators).buildApi(uri) + then: + result.validationResults.size() == 7 + } } diff --git a/ctp-validators/src/test/resources/parameter-minmax-abbreviation-rule.raml b/ctp-validators/src/test/resources/parameter-minmax-abbreviation-rule.raml new file mode 100644 index 00000000..be17ce4f --- /dev/null +++ b/ctp-validators/src/test/resources/parameter-minmax-abbreviation-rule.raml @@ -0,0 +1,17 @@ +#%RAML 1.0 +title: parameter min max abbreviation rule + +/resources: + get: + queryParameters: + minimumQuantity: string + maximumPrice: string + minimum: string + maximum: string + minimumExcluded: string + minQuantity: string + maxPrice: string + headers: + minimumRetry: string + maximumRetry: string + maxRetry: string From dd8be034bb161ae733ce335320e8e18f02adb93a Mon Sep 17 00:00:00 2001 From: Mo Alras Date: Tue, 3 Mar 2026 14:56:27 +0100 Subject: [PATCH 38/45] changes --- .claude/settings.local.json | 34 ++ .claude/skills/ctp-validator-rule-creator.md | 407 +++++++++++++++++++ 2 files changed, 441 insertions(+) create mode 100644 .claude/settings.local.json create mode 100644 .claude/skills/ctp-validator-rule-creator.md diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 00000000..edc33cf3 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,34 @@ +{ + "permissions": { + "allow": [ + "WebFetch(domain:github.com)", + "WebFetch(domain:raw.githubusercontent.com)", + "Bash(grep -l \"minimum\\\\|maximum\" /Users/morass/Repos/rmf-codegen/ctp-validators/src/test/resources/*.raml)", + "Bash(./gradlew :ctp-validators:test)", + "Bash(find /Users/morass/Repos/sphere-backend -name \"*.raml\" -exec grep -l \"minimum\\\\|maximum\" {} ;)", + "Bash(xargs cat)", + "Bash(while read f)", + "Bash(do grep -H \"minimum\\\\|maximum\" \"$f\")", + "Bash(done)", + "Bash(shuf)", + "Bash(find /Users/morass/Repos/sphere-backend/platform/search-api/src/main/raml -name \"*.raml\" -type f -exec cat {} ;)", + "Bash(find /Users/morass/Repos/commercetools-docs/api-specs -type f -name \"*.raml\" -exec grep -H \"^\\\\s*\\\\\\(minimum\\\\|maximum\\\\\\)\" {} ;)", + "Bash(find /Users/morass/Repos/commercetools-docs/api-specs -type f -name \"*.raml\" -exec grep -H \"^\\\\s*\\\\\\(minimum[A-Z]\\\\|maximum[A-Z]\\\\\\)\" {} ;)", + "Bash(find /Users/morass/Repos/commercetools-docs/api-specs -type f -name \"*.raml\" -exec grep -l \"^\\\\s*\\\\\\(minimum\\\\|maximum\\\\\\)\" {} ;)", + "Bash(find /Users/morass/Repos/rmf-codegen/.claude/worktrees/bold-poitras -name \"*.raml\" -exec grep -l \"headers:\" {} ;)", + "Bash(git add ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/ParameterMinMaxAbbreviationRule.kt ctp-validators/src/test/resources/parameter-minmax-abbreviation-rule.raml ctp-validators/src/test/groovy/com/commercetools/rmf/validators/ValidatorRulesTest.groovy)", + "Bash(git commit:*)", + "Bash(git push origin claude/bold-poitras)", + "Bash(git add api-specs/README.md)", + "Bash(git push)", + "Bash(git add api-specs/readme.md)", + "mcp__fc210d30-797d-4966-8b55-8c3a3a9c5988__updateConfluencePage", + "Bash(brew --version)", + "Bash(/opt/homebrew/bin/brew --version)", + "Bash(/usr/local/bin/brew --version)", + "Bash(/opt/homebrew/bin/gh --version)", + "Bash(/usr/local/bin/gh --version)", + "Bash(python3 -c \":*)" + ] + } +} diff --git a/.claude/skills/ctp-validator-rule-creator.md b/.claude/skills/ctp-validator-rule-creator.md new file mode 100644 index 00000000..2250412e --- /dev/null +++ b/.claude/skills/ctp-validator-rule-creator.md @@ -0,0 +1,407 @@ +# CTP Validator Rule Creator + +This skill guides creation of new validation rules for the ctp-validators Kotlin project, including implementation, tests, violation detection, documentation, and Confluence updates. + +--- + +## End-to-End Workflow + +### Step 1: Implement the Rule (rmf-codegen) + +#### 1a. Create Rule Class + +**Location:** `ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/Rule.kt` + +```kotlin +package com.commercetools.rmf.validators + +import io.vrap.rmf.raml.model.types.* +import org.eclipse.emf.common.util.Diagnostic +import java.util.* + +@ValidatorSet +class Rule(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) + + // Choose the appropriate case method based on what you're validating: + // - caseObjectType(type: ObjectType) — for validating properties with access to parent type name + // - caseProperty(property: Property) — for validating individual properties + // - caseStringType(type: StringType) — for validating string/enum types + // - caseBooleanType(type: BooleanType) — for boolean types + // - caseArrayType(type: ArrayType) — for array types + override fun caseObjectType(type: ObjectType): List { + val validationResults: MutableList = ArrayList() + + type.properties.forEach { property -> + if (exclude.contains("${type.name}:${property.name}").not()) { + // Your validation logic here + if (/* condition */) { + validationResults.add(create(type, "Property \"{0}\" of type \"{1}\" ", property.name, type.name)) + } + } + } + return validationResults + } + + companion object : ValidatorFactory<Rule> { + private val defaultExcludes by lazy { listOf("") } + + @JvmStatic + override fun create(options: List?): Rule { + return Rule(RuleSeverity.ERROR, options) + } + + @JvmStatic + override fun create(severity: RuleSeverity, options: List?): Rule { + return Rule(severity, options) + } + } +} +``` + +**Key decisions:** +- Use `@ValidatorSet` annotation for automatic discovery (included in default set) +- Use `create()` (not `error()`) so severity is configurable via ruleset XML +- Extend `TypesRule` for type/property validation, `ResourcesRule` for HTTP method/resource validation, `ModulesRule` for module-level validation +- Exclusion format: `TypeName:propertyName` for property rules, `TypeName` for type-level rules, plain `paramName` for query parameter/header rules + +#### 1a-alt. ResourcesRule Template (for query parameter/header validation) + +If validating query parameters or headers, extend `ResourcesRule` instead of `TypesRule`: + +```kotlin +package com.commercetools.rmf.validators + +import io.vrap.rmf.raml.model.resources.Method +import org.eclipse.emf.common.util.Diagnostic +import java.util.* + +@ValidatorSet +class Rule(severity: RuleSeverity, options: List? = null) : ResourcesRule(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 caseMethod(method: Method): List { + val validationResults: MutableList = ArrayList() + + method.queryParameters.forEach { queryParameter -> + run { + if (exclude.contains(queryParameter.name).not() && queryParameter.pattern == null) { + if (/* condition */) { + validationResults.add(create(queryParameter, "Query parameter \"{0}\" ", queryParameter.name)) + } + } + } + } + + method.headers.forEach { header -> + run { + if (exclude.contains(header.name).not()) { + if (/* condition */) { + validationResults.add(create(header, "Header \"{0}\" ", header.name)) + } + } + } + } + + return validationResults + } + + companion object : ValidatorFactory<Rule> { + private val defaultExcludes by lazy { listOf("") } + + @JvmStatic + override fun create(options: List?): Rule { + return Rule(RuleSeverity.ERROR, options) + } + + @JvmStatic + override fun create(severity: RuleSeverity, options: List?): Rule { + return Rule(severity, options) + } + } +} +``` + +**Notes for ResourcesRule:** +- Use `ResourcesValidator` (not `TypesValidator`) in tests +- Access query params via `method.queryParameters`, headers via `method.headers` +- Skip pattern parameters with `queryParameter.pattern == null` +- Test RAML uses resource/method structure instead of `types:` block (see 1b-alt below) + +#### 1b. Create Test RAML File + +**Location:** `ctp-validators/src/test/resources/-rule.raml` + +```raml +#%RAML 1.0 +title: + +annotationTypes: + package: string + sdkBaseUri: string + +baseUri: https://api.europe-west1.commercetools.com + +types: + Invalid: + (package): Common + type: object + properties: + badProperty: + description: + type: string + + Valid: + (package): Common + type: object + properties: + goodProperty: + description: + type: string +``` + +#### 1b-alt. Test RAML for ResourcesRule (query parameters/headers) + +```raml +#%RAML 1.0 +title: + +/resources: + get: + queryParameters: + invalidParam: string + validParam: string + excludedParam: string + headers: + invalidHeader: string + validHeader: string +``` + +#### 1c. Add Test Methods + +**Location:** `ctp-validators/src/test/groovy/com/commercetools/rmf/validators/ValidatorRulesTest.groovy` + +```groovy +def " rule"() { + when: + def options = singletonList(new RuleOption(RuleOptionType.EXCLUDE.toString(), "TypeName:excludedProperty")) + def validators = Arrays.asList(new TypesValidator(Arrays.asList(Rule.create(options)))) + def uri = uriFromClasspath("/-rule.raml") + def result = new RamlModelBuilder(validators).buildApi(uri) + then: + result.validationResults.size() == + result.validationResults[0].message == "" +} + +def " rule without exclusions"() { + when: + def validators = Arrays.asList(new TypesValidator(Arrays.asList(Rule.create(emptyList())))) + def uri = uriFromClasspath("/-rule.raml") + def result = new RamlModelBuilder(validators).buildApi(uri) + then: + result.validationResults.size() == +} +``` + +**Validator classes by rule type:** +- `TypesValidator` — for rules extending `TypesRule` +- `ResourcesValidator` — for rules extending `ResourcesRule` +- `ModulesValidator` — for rules extending `ModulesRule` +- `ResolvedResourcesValidator` — for rules extending `ResolvedResourcesRule` + +#### 1d. Run Tests + +```bash +./gradlew :ctp-validators:test +``` + +#### 1e. Create PR for rmf-codegen + +1. Create a branch, stage and commit the 3 files: + - `Rule.kt` + - `-rule.raml` + - `ValidatorRulesTest.groovy` + +2. Push and create a PR against `commercetools/rmf-codegen` with: + - **Title:** `Add Rule validation rule` + - **Body:** Summary of what the rule validates, list of files changed, test results + +Create the PR using `gh` CLI: + +```bash +gh pr create --repo commercetools/rmf-codegen \ + --title "Add Rule validation rule" \ + --body "$(cat <<'EOF' +## Summary +- + +## Test plan +- [x] `./gradlew :ctp-validators:test` passes +- [x] Rule catches both prefix and standalone usage +- [x] Exclusions work correctly + +🤖 Generated with [Claude Code](https://claude.com/claude-code) +EOF +)" +``` + +**Fallback** (if `gh` is not installed): use the GitHub REST API with `curl`: + +```bash +curl -s -X POST \ + -H "Authorization: token " \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/commercetools/rmf-codegen/pulls" \ + -d '{ + "title": "Add Rule validation rule", + "body": "## Summary\n...", + "head": ":", + "base": "main" + }' +``` + +--- + +### Step 2: Detect Violations + +Search the API spec RAML files for existing violations. + +**Local repos:** +- sphere-backend: `/Users/morass/Repos/sphere-backend` (search-api RAML specs) +- commercetools-docs: `/Users/morass/Repos/commercetools-docs/api-specs/` (main API specs) + +**API spec locations in commercetools-docs:** +- `api-specs/api/` — main Composable Commerce API +- `api-specs/history/` — Change History API +- `api-specs/import/` — Import API +- `api-specs/checkout/` — Checkout API +- `api-specs/connect/` — Connect API +- `api-specs/frontend-api/` — Frontend API +- `api-specs/insights/` — Insights API +- `api-specs/mist/` — MIST API + +Use grep to search RAML files for violations matching the rule's pattern. + +--- + +### Step 3: Add Exceptions (commercetools-docs) + +**Ruleset files:** `api-specs//ruleset.xml` + +Add exclusions in the XML format: + +```xml + + com.commercetools.rmf.validators.Rule + + + + + +``` + +Create a branch, commit, push, and open a PR. + +--- + +### Step 4: Document the Rule (commercetools-docs) + +**File:** `api-specs/README.md` + +1. Add TOC entry (alphabetically sorted): + ``` + - [Rule](#rule) + ``` + +2. Add documentation section in the appropriate subsection of "3. Detailed Rule Descriptions": + - Section 3.1: RAML-Related Rules + - Section 3.2: Guidelines Conformity Rules + ``` + #### Rule + + + + ``` + Invalid: + ... + Valid: + ... + ``` + ``` + +--- + +### Step 5: Update Confluence + +**Page:** [Spec Validation Rules](https://commercetools.atlassian.net/wiki/spaces/ADGAG/pages/2947514403) +**Cloud ID:** `c6e52965-84b2-4904-af8d-211cbb69dc2c` + +Updates needed: +1. Summary table: increment implemented count, decrement not-implemented count +2. Rules table in the current content (use markdown format) +- `updateConfluencePage` to write updated content and save the page adding a version comment with a short summary of the update followed by "Claude Assisted" so it appears in the page history + +--- + +## Reference: Available Case Methods (TypesSwitch) + +| Method | Use Case | +|--------|----------| +| `caseObjectType(type: ObjectType)` | Validate object types and their properties | +| `caseProperty(property: Property)` | Validate individual properties | +| `caseStringType(type: StringType)` | Validate string types and enums | +| `caseBooleanType(type: BooleanType)` | Validate boolean types | +| `caseArrayType(type: ArrayType)` | Validate array types | +| `caseNumberType(type: NumberType)` | Validate number types | +| `caseIntegerType(type: IntegerType)` | Validate integer types | +| `caseUnionType(type: UnionType)` | Validate union types | + +## Reference: Available Case Methods (ResourcesSwitch) + +| Method | Use Case | +|--------|----------| +| `caseMethod(method: Method)` | Validate HTTP methods, query parameters, headers | +| `caseResource(resource: Resource)` | Validate resource URIs and URI parameters | +| `caseBodyContainer(bodyContainer: BodyContainer)` | Validate request/response bodies | + +**Accessing query parameters and headers in `caseMethod`:** +- `method.queryParameters` — collection of query parameters +- `method.headers` — collection of headers +- `queryParameter.name` / `header.name` — parameter/header name +- `queryParameter.pattern` — non-null for regex pattern parameters (skip these) + +## Reference: Helper Methods + +- `create(object, "message {0} {1}", arg0, arg1)` — creates diagnostic with rule's configured severity +- `error(object, "message {0}", arg0)` — creates ERROR diagnostic (ignores configured severity) +- `property.eContainer()` — access parent container (returns ObjectType when called on a property) +- `property.pattern` — non-null for dynamic/pattern properties (e.g., `/a-z/`) +- `StringCaseFormat.LOWER_CAMEL_CASE.apply(name)` — convert to camelCase for comparison + +## Reference: Common Exclusion Patterns + +| Format | Used By | Example | +|--------|---------|---------| +| `TypeName:propertyName` | BooleanPropertyNameRule, PropertyMinMaxAbbreviationRule | `PriceTier:minimumQuantity` | +| `TypeName:enumValue` | EnumValuePascalCaseRule | `MoneyType:centPrecision` | +| `TypeName` | AsMapRule, EnumValuePascalCaseRule (type-level) | `LocalizedString` | +| `propertyName` | CamelCaseRule, PropertyPluralRule | `error_description` | +| `paramName` | QueryParameterCamelCaseRule, ParameterMinMaxAbbreviationRule | `minimumExcluded` | +| `resourcePath` | ResourcePluralRule | `inventory` | + +## Reference: Key Files + +| File | Purpose | +|------|---------| +| `ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/` | Rule implementations | +| `ctp-validators/src/test/groovy/com/commercetools/rmf/validators/ValidatorRulesTest.groovy` | All test cases | +| `ctp-validators/src/test/resources/` | Test RAML files | +| `ctp-validators/src/main/resources/ruleset.xml` | Default ruleset (disable rules here) | +| `ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/ValidatorSetup.kt` | Rule discovery mechanism | +| `.github/agents/ctp-validator-rule-creator.agent.md` | Agent instructions (reference) | From 4ff6ac84e0976ef033e1c5f8a2db09c66ba85b0e Mon Sep 17 00:00:00 2001 From: Mo Alras Date: Thu, 12 Mar 2026 10:28:32 +0100 Subject: [PATCH 39/45] Add test cases for properties/params without descriptions Address PR review: add test coverage for RAML properties and parameters defined without descriptions (non-inline types) to ensure the rule handles both forms correctly. Co-Authored-By: Claude Opus 4.6 --- .../rmf/validators/ValidatorRulesTest.groovy | 16 ++++++++++------ .../parameter-minmax-abbreviation-rule.raml | 5 +++++ .../property-minmax-abbreviation-rule.raml | 6 ++++++ 3 files changed, 21 insertions(+), 6 deletions(-) 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 099205c7..fab0dcaa 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 @@ -482,13 +482,15 @@ class ValidatorRulesTest extends Specification implements ValidatorFixtures { def uri = uriFromClasspath("/property-minmax-abbreviation-rule.raml") def result = new RamlModelBuilder(validators).buildApi(uri) then: - result.validationResults.size() == 6 + result.validationResults.size() == 8 result.validationResults[0].message == "Property \"minimumQuantity\" of type \"InvalidMinMax\" must use \"min\"/\"max\" instead of \"minimum\"/\"maximum\"" result.validationResults[1].message == "Property \"maximumPrice\" of type \"InvalidMinMax\" must use \"min\"/\"max\" instead of \"minimum\"/\"maximum\"" result.validationResults[2].message == "Property \"minimumOrder\" of type \"InvalidMinMax\" must use \"min\"/\"max\" instead of \"minimum\"/\"maximum\"" result.validationResults[3].message == "Property \"maximumItems\" of type \"InvalidMinMax\" must use \"min\"/\"max\" instead of \"minimum\"/\"maximum\"" result.validationResults[4].message == "Property \"minimum\" of type \"InvalidMinMax\" must use \"min\"/\"max\" instead of \"minimum\"/\"maximum\"" result.validationResults[5].message == "Property \"maximum\" of type \"InvalidMinMax\" must use \"min\"/\"max\" instead of \"minimum\"/\"maximum\"" + result.validationResults[6].message == "Property \"minimumTypeNumber\" of type \"InvalidMinMax\" must use \"min\"/\"max\" instead of \"minimum\"/\"maximum\"" + result.validationResults[7].message == "Property \"minimumNumber\" of type \"InvalidMinMax\" must use \"min\"/\"max\" instead of \"minimum\"/\"maximum\"" } def "property min max abbreviation rule without exclusions"() { @@ -497,7 +499,7 @@ class ValidatorRulesTest extends Specification implements ValidatorFixtures { def uri = uriFromClasspath("/property-minmax-abbreviation-rule.raml") def result = new RamlModelBuilder(validators).buildApi(uri) then: - result.validationResults.size() == 7 + result.validationResults.size() == 9 } def "parameter min max abbreviation rule"() { @@ -507,13 +509,15 @@ class ValidatorRulesTest extends Specification implements ValidatorFixtures { def uri = uriFromClasspath("/parameter-minmax-abbreviation-rule.raml") def result = new RamlModelBuilder(validators).buildApi(uri) then: - result.validationResults.size() == 6 + result.validationResults.size() == 8 result.validationResults[0].message == "Query parameter \"minimumQuantity\" must use \"min\"/\"max\" instead of \"minimum\"/\"maximum\"" result.validationResults[1].message == "Query parameter \"maximumPrice\" must use \"min\"/\"max\" instead of \"minimum\"/\"maximum\"" result.validationResults[2].message == "Query parameter \"minimum\" must use \"min\"/\"max\" instead of \"minimum\"/\"maximum\"" result.validationResults[3].message == "Query parameter \"maximum\" must use \"min\"/\"max\" instead of \"minimum\"/\"maximum\"" - result.validationResults[4].message == "Header \"minimumRetry\" must use \"min\"/\"max\" instead of \"minimum\"/\"maximum\"" - result.validationResults[5].message == "Header \"maximumRetry\" must use \"min\"/\"max\" instead of \"minimum\"/\"maximum\"" + result.validationResults[4].message == "Query parameter \"minimumTypeNumber\" must use \"min\"/\"max\" instead of \"minimum\"/\"maximum\"" + result.validationResults[5].message == "Query parameter \"minimumDescNumber\" must use \"min\"/\"max\" instead of \"minimum\"/\"maximum\"" + result.validationResults[6].message == "Header \"minimumRetry\" must use \"min\"/\"max\" instead of \"minimum\"/\"maximum\"" + result.validationResults[7].message == "Header \"maximumRetry\" must use \"min\"/\"max\" instead of \"minimum\"/\"maximum\"" } def "parameter min max abbreviation rule without exclusions"() { @@ -522,6 +526,6 @@ class ValidatorRulesTest extends Specification implements ValidatorFixtures { def uri = uriFromClasspath("/parameter-minmax-abbreviation-rule.raml") def result = new RamlModelBuilder(validators).buildApi(uri) then: - result.validationResults.size() == 7 + result.validationResults.size() == 9 } } diff --git a/ctp-validators/src/test/resources/parameter-minmax-abbreviation-rule.raml b/ctp-validators/src/test/resources/parameter-minmax-abbreviation-rule.raml index be17ce4f..2ae16743 100644 --- a/ctp-validators/src/test/resources/parameter-minmax-abbreviation-rule.raml +++ b/ctp-validators/src/test/resources/parameter-minmax-abbreviation-rule.raml @@ -9,6 +9,11 @@ title: parameter min max abbreviation rule minimum: string maximum: string minimumExcluded: string + minimumTypeNumber: + type: string + minimumDescNumber: + description: Lorem Ipsum + type: string minQuantity: string maxPrice: string headers: diff --git a/ctp-validators/src/test/resources/property-minmax-abbreviation-rule.raml b/ctp-validators/src/test/resources/property-minmax-abbreviation-rule.raml index 5df31a69..a1c9c2ac 100644 --- a/ctp-validators/src/test/resources/property-minmax-abbreviation-rule.raml +++ b/ctp-validators/src/test/resources/property-minmax-abbreviation-rule.raml @@ -33,6 +33,9 @@ types: minimumExcluded: description: will be excluded via options type: string + minimumTypeNumber: + type: number + minimumNumber: number ValidMinMax: (package): Common @@ -53,3 +56,6 @@ types: validName: description: unrelated property name type: string + minNumber: number + minTypeNumber: + type: number From 766a839429aa60e15f31ddeaa4fc1d56c4ec6dae Mon Sep 17 00:00:00 2001 From: Mo Alras Date: Thu, 12 Mar 2026 14:38:34 +0100 Subject: [PATCH 40/45] Remove .claude folder from tracked files Co-Authored-By: Claude Opus 4.6 --- .claude/settings.local.json | 34 -- .claude/skills/ctp-validator-rule-creator.md | 407 ------------------- 2 files changed, 441 deletions(-) delete mode 100644 .claude/settings.local.json delete mode 100644 .claude/skills/ctp-validator-rule-creator.md diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index edc33cf3..00000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "permissions": { - "allow": [ - "WebFetch(domain:github.com)", - "WebFetch(domain:raw.githubusercontent.com)", - "Bash(grep -l \"minimum\\\\|maximum\" /Users/morass/Repos/rmf-codegen/ctp-validators/src/test/resources/*.raml)", - "Bash(./gradlew :ctp-validators:test)", - "Bash(find /Users/morass/Repos/sphere-backend -name \"*.raml\" -exec grep -l \"minimum\\\\|maximum\" {} ;)", - "Bash(xargs cat)", - "Bash(while read f)", - "Bash(do grep -H \"minimum\\\\|maximum\" \"$f\")", - "Bash(done)", - "Bash(shuf)", - "Bash(find /Users/morass/Repos/sphere-backend/platform/search-api/src/main/raml -name \"*.raml\" -type f -exec cat {} ;)", - "Bash(find /Users/morass/Repos/commercetools-docs/api-specs -type f -name \"*.raml\" -exec grep -H \"^\\\\s*\\\\\\(minimum\\\\|maximum\\\\\\)\" {} ;)", - "Bash(find /Users/morass/Repos/commercetools-docs/api-specs -type f -name \"*.raml\" -exec grep -H \"^\\\\s*\\\\\\(minimum[A-Z]\\\\|maximum[A-Z]\\\\\\)\" {} ;)", - "Bash(find /Users/morass/Repos/commercetools-docs/api-specs -type f -name \"*.raml\" -exec grep -l \"^\\\\s*\\\\\\(minimum\\\\|maximum\\\\\\)\" {} ;)", - "Bash(find /Users/morass/Repos/rmf-codegen/.claude/worktrees/bold-poitras -name \"*.raml\" -exec grep -l \"headers:\" {} ;)", - "Bash(git add ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/ParameterMinMaxAbbreviationRule.kt ctp-validators/src/test/resources/parameter-minmax-abbreviation-rule.raml ctp-validators/src/test/groovy/com/commercetools/rmf/validators/ValidatorRulesTest.groovy)", - "Bash(git commit:*)", - "Bash(git push origin claude/bold-poitras)", - "Bash(git add api-specs/README.md)", - "Bash(git push)", - "Bash(git add api-specs/readme.md)", - "mcp__fc210d30-797d-4966-8b55-8c3a3a9c5988__updateConfluencePage", - "Bash(brew --version)", - "Bash(/opt/homebrew/bin/brew --version)", - "Bash(/usr/local/bin/brew --version)", - "Bash(/opt/homebrew/bin/gh --version)", - "Bash(/usr/local/bin/gh --version)", - "Bash(python3 -c \":*)" - ] - } -} diff --git a/.claude/skills/ctp-validator-rule-creator.md b/.claude/skills/ctp-validator-rule-creator.md deleted file mode 100644 index 2250412e..00000000 --- a/.claude/skills/ctp-validator-rule-creator.md +++ /dev/null @@ -1,407 +0,0 @@ -# CTP Validator Rule Creator - -This skill guides creation of new validation rules for the ctp-validators Kotlin project, including implementation, tests, violation detection, documentation, and Confluence updates. - ---- - -## End-to-End Workflow - -### Step 1: Implement the Rule (rmf-codegen) - -#### 1a. Create Rule Class - -**Location:** `ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/Rule.kt` - -```kotlin -package com.commercetools.rmf.validators - -import io.vrap.rmf.raml.model.types.* -import org.eclipse.emf.common.util.Diagnostic -import java.util.* - -@ValidatorSet -class Rule(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) - - // Choose the appropriate case method based on what you're validating: - // - caseObjectType(type: ObjectType) — for validating properties with access to parent type name - // - caseProperty(property: Property) — for validating individual properties - // - caseStringType(type: StringType) — for validating string/enum types - // - caseBooleanType(type: BooleanType) — for boolean types - // - caseArrayType(type: ArrayType) — for array types - override fun caseObjectType(type: ObjectType): List { - val validationResults: MutableList = ArrayList() - - type.properties.forEach { property -> - if (exclude.contains("${type.name}:${property.name}").not()) { - // Your validation logic here - if (/* condition */) { - validationResults.add(create(type, "Property \"{0}\" of type \"{1}\" ", property.name, type.name)) - } - } - } - return validationResults - } - - companion object : ValidatorFactory<Rule> { - private val defaultExcludes by lazy { listOf("") } - - @JvmStatic - override fun create(options: List?): Rule { - return Rule(RuleSeverity.ERROR, options) - } - - @JvmStatic - override fun create(severity: RuleSeverity, options: List?): Rule { - return Rule(severity, options) - } - } -} -``` - -**Key decisions:** -- Use `@ValidatorSet` annotation for automatic discovery (included in default set) -- Use `create()` (not `error()`) so severity is configurable via ruleset XML -- Extend `TypesRule` for type/property validation, `ResourcesRule` for HTTP method/resource validation, `ModulesRule` for module-level validation -- Exclusion format: `TypeName:propertyName` for property rules, `TypeName` for type-level rules, plain `paramName` for query parameter/header rules - -#### 1a-alt. ResourcesRule Template (for query parameter/header validation) - -If validating query parameters or headers, extend `ResourcesRule` instead of `TypesRule`: - -```kotlin -package com.commercetools.rmf.validators - -import io.vrap.rmf.raml.model.resources.Method -import org.eclipse.emf.common.util.Diagnostic -import java.util.* - -@ValidatorSet -class Rule(severity: RuleSeverity, options: List? = null) : ResourcesRule(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 caseMethod(method: Method): List { - val validationResults: MutableList = ArrayList() - - method.queryParameters.forEach { queryParameter -> - run { - if (exclude.contains(queryParameter.name).not() && queryParameter.pattern == null) { - if (/* condition */) { - validationResults.add(create(queryParameter, "Query parameter \"{0}\" ", queryParameter.name)) - } - } - } - } - - method.headers.forEach { header -> - run { - if (exclude.contains(header.name).not()) { - if (/* condition */) { - validationResults.add(create(header, "Header \"{0}\" ", header.name)) - } - } - } - } - - return validationResults - } - - companion object : ValidatorFactory<Rule> { - private val defaultExcludes by lazy { listOf("") } - - @JvmStatic - override fun create(options: List?): Rule { - return Rule(RuleSeverity.ERROR, options) - } - - @JvmStatic - override fun create(severity: RuleSeverity, options: List?): Rule { - return Rule(severity, options) - } - } -} -``` - -**Notes for ResourcesRule:** -- Use `ResourcesValidator` (not `TypesValidator`) in tests -- Access query params via `method.queryParameters`, headers via `method.headers` -- Skip pattern parameters with `queryParameter.pattern == null` -- Test RAML uses resource/method structure instead of `types:` block (see 1b-alt below) - -#### 1b. Create Test RAML File - -**Location:** `ctp-validators/src/test/resources/-rule.raml` - -```raml -#%RAML 1.0 -title: - -annotationTypes: - package: string - sdkBaseUri: string - -baseUri: https://api.europe-west1.commercetools.com - -types: - Invalid: - (package): Common - type: object - properties: - badProperty: - description: - type: string - - Valid: - (package): Common - type: object - properties: - goodProperty: - description: - type: string -``` - -#### 1b-alt. Test RAML for ResourcesRule (query parameters/headers) - -```raml -#%RAML 1.0 -title: - -/resources: - get: - queryParameters: - invalidParam: string - validParam: string - excludedParam: string - headers: - invalidHeader: string - validHeader: string -``` - -#### 1c. Add Test Methods - -**Location:** `ctp-validators/src/test/groovy/com/commercetools/rmf/validators/ValidatorRulesTest.groovy` - -```groovy -def " rule"() { - when: - def options = singletonList(new RuleOption(RuleOptionType.EXCLUDE.toString(), "TypeName:excludedProperty")) - def validators = Arrays.asList(new TypesValidator(Arrays.asList(Rule.create(options)))) - def uri = uriFromClasspath("/-rule.raml") - def result = new RamlModelBuilder(validators).buildApi(uri) - then: - result.validationResults.size() == - result.validationResults[0].message == "" -} - -def " rule without exclusions"() { - when: - def validators = Arrays.asList(new TypesValidator(Arrays.asList(Rule.create(emptyList())))) - def uri = uriFromClasspath("/-rule.raml") - def result = new RamlModelBuilder(validators).buildApi(uri) - then: - result.validationResults.size() == -} -``` - -**Validator classes by rule type:** -- `TypesValidator` — for rules extending `TypesRule` -- `ResourcesValidator` — for rules extending `ResourcesRule` -- `ModulesValidator` — for rules extending `ModulesRule` -- `ResolvedResourcesValidator` — for rules extending `ResolvedResourcesRule` - -#### 1d. Run Tests - -```bash -./gradlew :ctp-validators:test -``` - -#### 1e. Create PR for rmf-codegen - -1. Create a branch, stage and commit the 3 files: - - `Rule.kt` - - `-rule.raml` - - `ValidatorRulesTest.groovy` - -2. Push and create a PR against `commercetools/rmf-codegen` with: - - **Title:** `Add Rule validation rule` - - **Body:** Summary of what the rule validates, list of files changed, test results - -Create the PR using `gh` CLI: - -```bash -gh pr create --repo commercetools/rmf-codegen \ - --title "Add Rule validation rule" \ - --body "$(cat <<'EOF' -## Summary -- - -## Test plan -- [x] `./gradlew :ctp-validators:test` passes -- [x] Rule catches both prefix and standalone usage -- [x] Exclusions work correctly - -🤖 Generated with [Claude Code](https://claude.com/claude-code) -EOF -)" -``` - -**Fallback** (if `gh` is not installed): use the GitHub REST API with `curl`: - -```bash -curl -s -X POST \ - -H "Authorization: token " \ - -H "Accept: application/vnd.github+json" \ - "https://api.github.com/repos/commercetools/rmf-codegen/pulls" \ - -d '{ - "title": "Add Rule validation rule", - "body": "## Summary\n...", - "head": ":", - "base": "main" - }' -``` - ---- - -### Step 2: Detect Violations - -Search the API spec RAML files for existing violations. - -**Local repos:** -- sphere-backend: `/Users/morass/Repos/sphere-backend` (search-api RAML specs) -- commercetools-docs: `/Users/morass/Repos/commercetools-docs/api-specs/` (main API specs) - -**API spec locations in commercetools-docs:** -- `api-specs/api/` — main Composable Commerce API -- `api-specs/history/` — Change History API -- `api-specs/import/` — Import API -- `api-specs/checkout/` — Checkout API -- `api-specs/connect/` — Connect API -- `api-specs/frontend-api/` — Frontend API -- `api-specs/insights/` — Insights API -- `api-specs/mist/` — MIST API - -Use grep to search RAML files for violations matching the rule's pattern. - ---- - -### Step 3: Add Exceptions (commercetools-docs) - -**Ruleset files:** `api-specs//ruleset.xml` - -Add exclusions in the XML format: - -```xml - - com.commercetools.rmf.validators.Rule - - - - - -``` - -Create a branch, commit, push, and open a PR. - ---- - -### Step 4: Document the Rule (commercetools-docs) - -**File:** `api-specs/README.md` - -1. Add TOC entry (alphabetically sorted): - ``` - - [Rule](#rule) - ``` - -2. Add documentation section in the appropriate subsection of "3. Detailed Rule Descriptions": - - Section 3.1: RAML-Related Rules - - Section 3.2: Guidelines Conformity Rules - ``` - #### Rule - - - - ``` - Invalid: - ... - Valid: - ... - ``` - ``` - ---- - -### Step 5: Update Confluence - -**Page:** [Spec Validation Rules](https://commercetools.atlassian.net/wiki/spaces/ADGAG/pages/2947514403) -**Cloud ID:** `c6e52965-84b2-4904-af8d-211cbb69dc2c` - -Updates needed: -1. Summary table: increment implemented count, decrement not-implemented count -2. Rules table in the current content (use markdown format) -- `updateConfluencePage` to write updated content and save the page adding a version comment with a short summary of the update followed by "Claude Assisted" so it appears in the page history - ---- - -## Reference: Available Case Methods (TypesSwitch) - -| Method | Use Case | -|--------|----------| -| `caseObjectType(type: ObjectType)` | Validate object types and their properties | -| `caseProperty(property: Property)` | Validate individual properties | -| `caseStringType(type: StringType)` | Validate string types and enums | -| `caseBooleanType(type: BooleanType)` | Validate boolean types | -| `caseArrayType(type: ArrayType)` | Validate array types | -| `caseNumberType(type: NumberType)` | Validate number types | -| `caseIntegerType(type: IntegerType)` | Validate integer types | -| `caseUnionType(type: UnionType)` | Validate union types | - -## Reference: Available Case Methods (ResourcesSwitch) - -| Method | Use Case | -|--------|----------| -| `caseMethod(method: Method)` | Validate HTTP methods, query parameters, headers | -| `caseResource(resource: Resource)` | Validate resource URIs and URI parameters | -| `caseBodyContainer(bodyContainer: BodyContainer)` | Validate request/response bodies | - -**Accessing query parameters and headers in `caseMethod`:** -- `method.queryParameters` — collection of query parameters -- `method.headers` — collection of headers -- `queryParameter.name` / `header.name` — parameter/header name -- `queryParameter.pattern` — non-null for regex pattern parameters (skip these) - -## Reference: Helper Methods - -- `create(object, "message {0} {1}", arg0, arg1)` — creates diagnostic with rule's configured severity -- `error(object, "message {0}", arg0)` — creates ERROR diagnostic (ignores configured severity) -- `property.eContainer()` — access parent container (returns ObjectType when called on a property) -- `property.pattern` — non-null for dynamic/pattern properties (e.g., `/a-z/`) -- `StringCaseFormat.LOWER_CAMEL_CASE.apply(name)` — convert to camelCase for comparison - -## Reference: Common Exclusion Patterns - -| Format | Used By | Example | -|--------|---------|---------| -| `TypeName:propertyName` | BooleanPropertyNameRule, PropertyMinMaxAbbreviationRule | `PriceTier:minimumQuantity` | -| `TypeName:enumValue` | EnumValuePascalCaseRule | `MoneyType:centPrecision` | -| `TypeName` | AsMapRule, EnumValuePascalCaseRule (type-level) | `LocalizedString` | -| `propertyName` | CamelCaseRule, PropertyPluralRule | `error_description` | -| `paramName` | QueryParameterCamelCaseRule, ParameterMinMaxAbbreviationRule | `minimumExcluded` | -| `resourcePath` | ResourcePluralRule | `inventory` | - -## Reference: Key Files - -| File | Purpose | -|------|---------| -| `ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/` | Rule implementations | -| `ctp-validators/src/test/groovy/com/commercetools/rmf/validators/ValidatorRulesTest.groovy` | All test cases | -| `ctp-validators/src/test/resources/` | Test RAML files | -| `ctp-validators/src/main/resources/ruleset.xml` | Default ruleset (disable rules here) | -| `ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/ValidatorSetup.kt` | Rule discovery mechanism | -| `.github/agents/ctp-validator-rule-creator.agent.md` | Agent instructions (reference) | From 2f185f5a30a8f78897f5f8f5d85923c1fb19ebf1 Mon Sep 17 00:00:00 2001 From: Jens Schulze Date: Thu, 19 Mar 2026 14:13:10 +0100 Subject: [PATCH 41/45] add urlencode to path parameters --- .../io/vrap/codegen/languages/php/model/PhpMethodRenderer.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/languages/php/src/main/kotlin/io/vrap/codegen/languages/php/model/PhpMethodRenderer.kt b/languages/php/src/main/kotlin/io/vrap/codegen/languages/php/model/PhpMethodRenderer.kt index b0d717d7..9f62d839 100644 --- a/languages/php/src/main/kotlin/io/vrap/codegen/languages/php/model/PhpMethodRenderer.kt +++ b/languages/php/src/main/kotlin/io/vrap/codegen/languages/php/model/PhpMethodRenderer.kt @@ -111,7 +111,7 @@ class PhpMethodRenderer constructor(override val vrapTypeProvider: VrapTypeProvi | $!uri = str_replace([${ type.allParams()?.joinToString(separator = ", ") { "'{$it}'" } ?: "" }], [${ - type.allParams()?.joinToString(separator = ", ") { "$$it" } ?: "" + type.allParams()?.joinToString(separator = ", ") { "urlencode($$it)" } ?: "" }], '${type.apiResource().fullUri.template.trimStart('/')}'); | <<${type.firstBody()?.ensureContentType() ?: ""}>> | <<${ From ae370158c6a015148a22dfef099b83e5705e578e Mon Sep 17 00:00:00 2001 From: Auto Mation Date: Thu, 19 Mar 2026 13:26:35 +0000 Subject: [PATCH 42/45] TASK: Updating version in README --- scripts/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/install.sh b/scripts/install.sh index dea7c701..1b0f6c17 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -2,7 +2,7 @@ set -e -CODEGEN_VERSION=${VRAP_VERSION:-"1.0.0-20260226134024"} +CODEGEN_VERSION=${VRAP_VERSION:-"1.0.0-20260319131529"} CLI_HOME=~/.rmf-cli LIB_FOLDER=$CLI_HOME/lib JAR_FILE_PATH=$LIB_FOLDER/codegen-cli-${CODEGEN_VERSION}.jar From c384cba672e1920dc1cb4b6dac72320f49d287a0 Mon Sep 17 00:00:00 2001 From: Mo Alras Date: Tue, 10 Mar 2026 08:28:44 +0100 Subject: [PATCH 43/45] Add ErrorResponseStructureRule validation rule Validates that error response body types (HTTP 4xx/5xx) follow the API Design Guidelines structure: must have statusCode and message properties, should have an errors array with items containing code and message. Co-Authored-By: Claude Opus 4.6 --- .../validators/ErrorResponseStructureRule.kt | 74 +++++++++++ .../rmf/validators/ValidatorRulesTest.groovy | 25 ++++ .../error-response-structure-rule.raml | 125 ++++++++++++++++++ 3 files changed, 224 insertions(+) create mode 100644 ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/ErrorResponseStructureRule.kt create mode 100644 ctp-validators/src/test/resources/error-response-structure-rule.raml diff --git a/ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/ErrorResponseStructureRule.kt b/ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/ErrorResponseStructureRule.kt new file mode 100644 index 00000000..b24e4102 --- /dev/null +++ b/ctp-validators/src/main/kotlin/com/commercetools/rmf/validators/ErrorResponseStructureRule.kt @@ -0,0 +1,74 @@ +package com.commercetools.rmf.validators + +import io.vrap.rmf.raml.model.resources.Method +import io.vrap.rmf.raml.model.resources.Resource +import io.vrap.rmf.raml.model.types.ArrayType +import io.vrap.rmf.raml.model.types.ObjectType +import org.eclipse.emf.common.util.Diagnostic +import java.util.* + +@ValidatorSet +class ErrorResponseStructureRule(severity: RuleSeverity, options: List? = null) : ResourcesRule(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 caseMethod(method: Method): List { + val validationResults: MutableList = ArrayList() + val resource = method.eContainer() as Resource + val methodId = "${method.method.name} ${resource.fullUri.template}" + + if (exclude.contains(methodId)) { + return validationResults + } + + val errorResponses = method.responses.filter { response -> response.statusCode.toInt() >= 400 } + + errorResponses.forEach { response -> + val statusCode = response.statusCode + response.bodies.forEach { body -> + val bodyType = body.type + if (bodyType is ObjectType) { + if (bodyType.properties.none { it.name == "statusCode" }) { + validationResults.add(create(method, "Method \"{0} {1}\" error response (status {2}) body type must have a \"statusCode\" property", method.method.name, resource.fullUri.template, statusCode)) + } + + if (bodyType.properties.none { it.name == "message" }) { + validationResults.add(create(method, "Method \"{0} {1}\" error response (status {2}) body type must have a \"message\" property", method.method.name, resource.fullUri.template, statusCode)) + } + + val errorsProperty = bodyType.properties.find { it.name == "errors" } + if (errorsProperty == null) { + validationResults.add(warning(method, "Method \"{0} {1}\" error response (status {2}) body type should have an \"errors\" array property", method.method.name, resource.fullUri.template, statusCode)) + } else if (errorsProperty.type is ArrayType) { + val itemsType = (errorsProperty.type as ArrayType).items + if (itemsType is ObjectType) { + if (itemsType.properties.none { it.name == "code" }) { + validationResults.add(create(method, "Method \"{0} {1}\" error response (status {2}) error items must have a \"code\" property", method.method.name, resource.fullUri.template, statusCode)) + } + if (itemsType.properties.none { it.name == "message" }) { + validationResults.add(create(method, "Method \"{0} {1}\" error response (status {2}) error items must have a \"message\" property", method.method.name, resource.fullUri.template, statusCode)) + } + } + } + } + } + } + + return validationResults + } + + companion object : ValidatorFactory { + private val defaultExcludes by lazy { listOf("") } + + @JvmStatic + override fun create(options: List?): ErrorResponseStructureRule { + return ErrorResponseStructureRule(RuleSeverity.ERROR, options) + } + + @JvmStatic + override fun create(severity: RuleSeverity, options: List?): ErrorResponseStructureRule { + return ErrorResponseStructureRule(severity, options) + } + } +} 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 fab0dcaa..ee6d7932 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 @@ -528,4 +528,29 @@ class ValidatorRulesTest extends Specification implements ValidatorFixtures { then: result.validationResults.size() == 9 } + + def "error response structure rule"() { + when: + def options = singletonList(new RuleOption(RuleOptionType.EXCLUDE.toString(), "GET /excluded")) + def validators = Arrays.asList(new ResourcesValidator(Arrays.asList(ErrorResponseStructureRule.create(options)))) + def uri = uriFromClasspath("/error-response-structure-rule.raml") + def result = new RamlModelBuilder(validators).buildApi(uri) + then: + result.validationResults.size() == 5 + result.validationResults[0].message == "Method \"GET /missing-status-code\" error response (status 400) body type must have a \"statusCode\" property" + result.validationResults[1].message == "Method \"GET /missing-message\" error response (status 404) body type must have a \"message\" property" + result.validationResults[2].message == "Method \"GET /missing-errors-array\" error response (status 500) body type should have an \"errors\" array property" + result.validationResults[2].severity == Diagnostic.WARNING + result.validationResults[3].message == "Method \"GET /invalid-error-items\" error response (status 400) error items must have a \"code\" property" + result.validationResults[4].message == "Method \"GET /invalid-error-items\" error response (status 400) error items must have a \"message\" property" + } + + def "error response structure rule without exclusions"() { + when: + def validators = Arrays.asList(new ResourcesValidator(Arrays.asList(ErrorResponseStructureRule.create(emptyList())))) + def uri = uriFromClasspath("/error-response-structure-rule.raml") + def result = new RamlModelBuilder(validators).buildApi(uri) + then: + result.validationResults.size() == 6 + } } diff --git a/ctp-validators/src/test/resources/error-response-structure-rule.raml b/ctp-validators/src/test/resources/error-response-structure-rule.raml new file mode 100644 index 00000000..9eaceb20 --- /dev/null +++ b/ctp-validators/src/test/resources/error-response-structure-rule.raml @@ -0,0 +1,125 @@ +#%RAML 1.0 +title: error response structure rule + +types: + ValidErrorResponse: + type: object + properties: + statusCode: + type: integer + message: + type: string + errors: + type: array + items: + type: object + properties: + code: + type: string + message: + type: string + + MissingStatusCodeError: + type: object + properties: + message: + type: string + errors: + type: array + items: + type: object + properties: + code: + type: string + message: + type: string + + MissingMessageError: + type: object + properties: + statusCode: + type: integer + errors: + type: array + items: + type: object + properties: + code: + type: string + message: + type: string + + MissingErrorsArrayError: + type: object + properties: + statusCode: + type: integer + message: + type: string + + InvalidErrorItems: + type: object + properties: + statusCode: + type: integer + message: + type: string + errors: + type: array + items: + type: object + properties: + detail: + type: string + +/valid: + get: + responses: + 200: + body: + application/json: + type: object + 400: + body: + application/json: + type: ValidErrorResponse + +/missing-status-code: + get: + responses: + 400: + body: + application/json: + type: MissingStatusCodeError + +/missing-message: + get: + responses: + 404: + body: + application/json: + type: MissingMessageError + +/missing-errors-array: + get: + responses: + 500: + body: + application/json: + type: MissingErrorsArrayError + +/invalid-error-items: + get: + responses: + 400: + body: + application/json: + type: InvalidErrorItems + +/excluded: + get: + responses: + 400: + body: + application/json: + type: MissingStatusCodeError From b35699e6ce04f7d4d7e67fb7d8e3617d3964b35a Mon Sep 17 00:00:00 2001 From: Mo Alras Date: Thu, 12 Mar 2026 10:46:10 +0100 Subject: [PATCH 44/45] Add descriptions to test RAML properties for inline type coverage Ensure the rule is tested with both described (inline type) and non-described property forms. Co-Authored-By: Claude Opus 4.6 --- .../src/test/resources/error-response-structure-rule.raml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ctp-validators/src/test/resources/error-response-structure-rule.raml b/ctp-validators/src/test/resources/error-response-structure-rule.raml index 9eaceb20..26445492 100644 --- a/ctp-validators/src/test/resources/error-response-structure-rule.raml +++ b/ctp-validators/src/test/resources/error-response-structure-rule.raml @@ -6,8 +6,10 @@ types: type: object properties: statusCode: + description: HTTP status code type: integer message: + description: Error message type: string errors: type: array @@ -15,6 +17,7 @@ types: type: object properties: code: + description: Error code type: string message: type: string @@ -38,6 +41,7 @@ types: type: object properties: statusCode: + description: HTTP status code type: integer errors: type: array @@ -47,6 +51,7 @@ types: code: type: string message: + description: Error message type: string MissingErrorsArrayError: @@ -63,6 +68,7 @@ types: statusCode: type: integer message: + description: Error message type: string errors: type: array @@ -70,6 +76,7 @@ types: type: object properties: detail: + description: Error detail type: string /valid: From 5209b546ac0b38912b5195371c27f8ca37b46c50 Mon Sep 17 00:00:00 2001 From: Jens Schulze Date: Thu, 9 Apr 2026 13:08:26 +0200 Subject: [PATCH 45/45] support combined unique property --- .../typescript/joi/JoiValidatorModuleRenderer.kt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/languages/typescript/src/main/kotlin/io/vrap/codegen/languages/typescript/joi/JoiValidatorModuleRenderer.kt b/languages/typescript/src/main/kotlin/io/vrap/codegen/languages/typescript/joi/JoiValidatorModuleRenderer.kt index 216948b0..c7898ab6 100644 --- a/languages/typescript/src/main/kotlin/io/vrap/codegen/languages/typescript/joi/JoiValidatorModuleRenderer.kt +++ b/languages/typescript/src/main/kotlin/io/vrap/codegen/languages/typescript/joi/JoiValidatorModuleRenderer.kt @@ -8,6 +8,7 @@ import io.vrap.codegen.languages.typescript.tsGeneratedComment import io.vrap.rmf.codegen.di.AllAnyTypes 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.keepIndentation import io.vrap.rmf.codegen.types.* import io.vrap.rmf.raml.model.types.* @@ -194,7 +195,13 @@ class JoiValidatorModuleRenderer constructor(override val vrapTypeProvider: Vrap vrapType.renderTypeRef() val uniqueAnnotation = this.getAnnotation("uniqueProperty") ?: this.type.getAnnotation("uniqueProperty") val uniquePropConstraint = if (uniqueAnnotation != null) ".unique(\"${(uniqueAnnotation.value as StringInstance).value}\")" else "" - return "${name}: ${joiSchema}${minConstraint}${maxConstraint}${patternConstraint}${discriminatorConstraint}${uniquePropConstraint}${requiredConstraint}" + val uniquePropsAnnotation = this.getAnnotation("uniqueProperties") ?: this.type.getAnnotation("uniqueProperties") + val uniquePropsConstraint = if (uniquePropsAnnotation != null) ".unique((a, b) => ${ + (uniquePropsAnnotation.value as ArrayInstance).value.joinToString( + " && " + ) { "a[\"${it.value}\"] === b[\"${it.value}\"]" } + })".escapeAll() else "" + return "${name}: ${joiSchema}${minConstraint}${maxConstraint}${patternConstraint}${discriminatorConstraint}${uniquePropConstraint}${uniquePropsConstraint}${requiredConstraint}" } private fun VrapType.renderTypeRef(): String {