diff --git a/java-client/build.gradle.kts b/java-client/build.gradle.kts index f450b4b240..629e960198 100644 --- a/java-client/build.gradle.kts +++ b/java-client/build.gradle.kts @@ -204,6 +204,10 @@ dependencies { // http://json-b.net/ implementation("jakarta.json.bind", "jakarta.json.bind-api", "2.0.0") + // EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + // https://projects.eclipse.org/projects/ee4j.ca + implementation("jakarta.annotation", "jakarta.annotation-api", "1.3.5") + // Apache 2.0 implementation("com.fasterxml.jackson.core", "jackson-core", jacksonVersion) diff --git a/java-client/src/generated/java/org/opensearch/client/opensearch/_types/OpenSearchVersionInfo.java b/java-client/src/generated/java/org/opensearch/client/opensearch/_types/OpenSearchVersionInfo.java index 083a376f36..2de1706223 100644 --- a/java-client/src/generated/java/org/opensearch/client/opensearch/_types/OpenSearchVersionInfo.java +++ b/java-client/src/generated/java/org/opensearch/client/opensearch/_types/OpenSearchVersionInfo.java @@ -38,6 +38,7 @@ import jakarta.json.stream.JsonGenerator; import java.util.function.Function; +import javax.annotation.Generated; import javax.annotation.Nullable; import org.opensearch.client.json.JsonpDeserializable; import org.opensearch.client.json.JsonpDeserializer; @@ -52,6 +53,7 @@ // typedef: _types.OpenSearchVersionInfo @JsonpDeserializable +@Generated("org.opensearch.client.codegen.CodeGenerator") public class OpenSearchVersionInfo implements JsonpSerializable { private final String buildDate; diff --git a/java-client/src/generated/java/org/opensearch/client/opensearch/core/InfoRequest.java b/java-client/src/generated/java/org/opensearch/client/opensearch/core/InfoRequest.java index 68aef9616f..ab8265f76e 100644 --- a/java-client/src/generated/java/org/opensearch/client/opensearch/core/InfoRequest.java +++ b/java-client/src/generated/java/org/opensearch/client/opensearch/core/InfoRequest.java @@ -36,6 +36,7 @@ package org.opensearch.client.opensearch.core; +import javax.annotation.Generated; import org.opensearch.client.opensearch._types.ErrorResponse; import org.opensearch.client.opensearch._types.RequestBase; import org.opensearch.client.transport.Endpoint; @@ -46,6 +47,7 @@ /** * Returns basic information about the cluster. */ +@Generated("org.opensearch.client.codegen.CodeGenerator") public class InfoRequest extends RequestBase { public InfoRequest() {} diff --git a/java-client/src/generated/java/org/opensearch/client/opensearch/core/InfoResponse.java b/java-client/src/generated/java/org/opensearch/client/opensearch/core/InfoResponse.java index 09b32aaae7..a8afc4e71d 100644 --- a/java-client/src/generated/java/org/opensearch/client/opensearch/core/InfoResponse.java +++ b/java-client/src/generated/java/org/opensearch/client/opensearch/core/InfoResponse.java @@ -38,6 +38,7 @@ import jakarta.json.stream.JsonGenerator; import java.util.function.Function; +import javax.annotation.Generated; import org.opensearch.client.json.JsonpDeserializable; import org.opensearch.client.json.JsonpDeserializer; import org.opensearch.client.json.JsonpMapper; @@ -52,6 +53,7 @@ // typedef: info.Response @JsonpDeserializable +@Generated("org.opensearch.client.codegen.CodeGenerator") public class InfoResponse implements JsonpSerializable { private final String clusterName; diff --git a/java-codegen/build.gradle.kts b/java-codegen/build.gradle.kts index c4e4daad66..2af5f88b7c 100644 --- a/java-codegen/build.gradle.kts +++ b/java-codegen/build.gradle.kts @@ -56,7 +56,7 @@ java { } application { - mainClass.set("org.opensearch.client.codegen.Main") + mainClass.set("org.opensearch.client.codegen.CodeGenerator") applicationDefaultJvmArgs = listOf( "--add-exports", "jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", "--add-exports", "jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", diff --git a/java-codegen/src/main/java/org/opensearch/client/codegen/Main.java b/java-codegen/src/main/java/org/opensearch/client/codegen/CodeGenerator.java similarity index 99% rename from java-codegen/src/main/java/org/opensearch/client/codegen/Main.java rename to java-codegen/src/main/java/org/opensearch/client/codegen/CodeGenerator.java index 910c82c2df..e3c6977a58 100644 --- a/java-codegen/src/main/java/org/opensearch/client/codegen/Main.java +++ b/java-codegen/src/main/java/org/opensearch/client/codegen/CodeGenerator.java @@ -31,7 +31,7 @@ import org.opensearch.client.codegen.model.SpecTransformer; import org.opensearch.client.codegen.openapi.OpenApiSpecification; -public class Main { +public class CodeGenerator { private static final Logger LOGGER = LogManager.getLogger(); private static final OperationGroup.Matcher OPERATION_MATCHER = OperationGroup.matcher().add(null, "info"); diff --git a/java-codegen/src/main/java/org/opensearch/client/codegen/model/ArrayShape.java b/java-codegen/src/main/java/org/opensearch/client/codegen/model/ArrayShape.java index c45e73fda9..97540564eb 100644 --- a/java-codegen/src/main/java/org/opensearch/client/codegen/model/ArrayShape.java +++ b/java-codegen/src/main/java/org/opensearch/client/codegen/model/ArrayShape.java @@ -14,8 +14,8 @@ public class ArrayShape extends ObjectShape { private final Field valueBodyField; - public ArrayShape(Namespace parent, String className, Type arrayType, String typedefName) { - super(parent, className, typedefName); + public ArrayShape(Namespace parent, String className, Type arrayType, String typedefName, String description) { + super(parent, className, typedefName, description); this.valueBodyField = new Field("_value_body", arrayType, true, "Response value.", null); } diff --git a/java-codegen/src/main/java/org/opensearch/client/codegen/model/EnumShape.java b/java-codegen/src/main/java/org/opensearch/client/codegen/model/EnumShape.java index 64cd36b76e..0bb7f5df46 100644 --- a/java-codegen/src/main/java/org/opensearch/client/codegen/model/EnumShape.java +++ b/java-codegen/src/main/java/org/opensearch/client/codegen/model/EnumShape.java @@ -11,16 +11,32 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import org.opensearch.client.codegen.utils.JavaClassKind; import org.opensearch.client.codegen.utils.Strings; public class EnumShape extends Shape { private final List variants; - public EnumShape(Namespace parent, String className, List variants, String typedefName) { - super(parent, className, typedefName); + public EnumShape(Namespace parent, String className, List variants, String typedefName, String description) { + super(parent, className, typedefName, description); this.variants = variants; } + @Override + public JavaClassKind getClassKind() { + return JavaClassKind.Enum; + } + + @Override + public Collection getAnnotations() { + return List.of(Types.Client.Json.JsonpDeserializable); + } + + @Override + public Collection getImplementsTypes() { + return List.of(Types.Client.Json.JsonEnum); + } + public Collection getVariants() { return Collections.unmodifiableCollection(variants); } diff --git a/java-codegen/src/main/java/org/opensearch/client/codegen/model/Namespace.java b/java-codegen/src/main/java/org/opensearch/client/codegen/model/Namespace.java index 6e313b0f48..7ca9322130 100644 --- a/java-codegen/src/main/java/org/opensearch/client/codegen/model/Namespace.java +++ b/java-codegen/src/main/java/org/opensearch/client/codegen/model/Namespace.java @@ -19,7 +19,8 @@ import org.opensearch.client.codegen.utils.Lists; import org.opensearch.client.codegen.utils.Strings; -public class Namespace extends Shape { +public class Namespace { + private final Namespace parent; private final String name; private final Map children = new TreeMap<>(); private final Map operations = new TreeMap<>(); @@ -30,7 +31,7 @@ public Namespace() { } private Namespace(Namespace parent, String name) { - super(parent, null, null); + this.parent = parent; this.name = name; } @@ -43,7 +44,6 @@ public void addShape(Shape shape) { shapes.add(shape); } - @Override public String getPackageName() { return parent != null ? parent.getPackageName() + "." + getPackageNamePart() : "org.opensearch.client.opensearch"; } @@ -66,7 +66,6 @@ public Namespace child(@Nullable String name) { return grandChildName == null ? child : child.child(grandChildName); } - @Override public void render(ShapeRenderingContext ctx) throws RenderException { for (Namespace child : children.values()) { child.render(ctx.forSubDir(child.getPackageNamePart())); @@ -87,10 +86,15 @@ private static class Client extends Shape { private final boolean async; private Client(Namespace parent, boolean async) { - super(parent, "OpenSearch" + Strings.toPascalCase(parent.name) + (async ? "Async" : "") + "Client", null); + super(parent, "OpenSearch" + Strings.toPascalCase(parent.name) + (async ? "Async" : "") + "Client", null, null); this.async = async; } + @Override + public Type getExtendsType() { + return Types.Client.ApiClient(Types.Client.Transport.OpenSearchTransport, getType()); + } + public String getName() { return parent.name; } diff --git a/java-codegen/src/main/java/org/opensearch/client/codegen/model/ObjectShape.java b/java-codegen/src/main/java/org/opensearch/client/codegen/model/ObjectShape.java index 91bcdbdfa6..3dcbd1127a 100644 --- a/java-codegen/src/main/java/org/opensearch/client/codegen/model/ObjectShape.java +++ b/java-codegen/src/main/java/org/opensearch/client/codegen/model/ObjectShape.java @@ -19,8 +19,8 @@ public class ObjectShape extends Shape { protected final Map bodyFields = new TreeMap<>(); protected Field additionalPropertiesField; - public ObjectShape(Namespace parent, String className, String typedefName) { - super(parent, className, typedefName); + public ObjectShape(Namespace parent, String className, String typedefName, String description) { + super(parent, className, typedefName, description); } public void addBodyField(Field field) { diff --git a/java-codegen/src/main/java/org/opensearch/client/codegen/model/RequestShape.java b/java-codegen/src/main/java/org/opensearch/client/codegen/model/RequestShape.java index 68e1d4ac22..3d0e25ef7e 100644 --- a/java-codegen/src/main/java/org/opensearch/client/codegen/model/RequestShape.java +++ b/java-codegen/src/main/java/org/opensearch/client/codegen/model/RequestShape.java @@ -25,8 +25,6 @@ public class RequestShape extends ObjectShape { @Nonnull private final OperationGroup operationGroup; - @Nullable - private final String description; @Nonnull private final Set httpMethods = new HashSet<>(); @Nonnull @@ -39,9 +37,8 @@ public class RequestShape extends ObjectShape { private final Map fields = new TreeMap<>(); public RequestShape(@Nonnull Namespace parent, @Nonnull OperationGroup operationGroup, @Nullable String description) { - super(parent, requestClassName(operationGroup), operationGroup + ".Request"); + super(parent, requestClassName(operationGroup), operationGroup + ".Request", description); this.operationGroup = operationGroup; - this.description = description; } @Nonnull @@ -53,11 +50,6 @@ public String getId() { return operationGroup.getName(); } - @Nullable - public String getDescription() { - return description; - } - public String getHttpMethod() { return Streams.sortedBy(httpMethods.stream(), m -> { switch (m) { diff --git a/java-codegen/src/main/java/org/opensearch/client/codegen/model/Shape.java b/java-codegen/src/main/java/org/opensearch/client/codegen/model/Shape.java index 7b8fcb166e..9b24cac8d9 100644 --- a/java-codegen/src/main/java/org/opensearch/client/codegen/model/Shape.java +++ b/java-codegen/src/main/java/org/opensearch/client/codegen/model/Shape.java @@ -8,12 +8,15 @@ package org.opensearch.client.codegen.model; +import java.util.Collection; +import java.util.Collections; import java.util.HashSet; import java.util.Set; import java.util.TreeSet; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.opensearch.client.codegen.exceptions.RenderException; +import org.opensearch.client.codegen.utils.JavaClassKind; public abstract class Shape { private static final Logger LOGGER = LogManager.getLogger(); @@ -21,19 +24,13 @@ public abstract class Shape { private final String className; private final Set referencedTypes = new HashSet<>(); private final String typedefName; + private final String description; - public Shape(Namespace parent, String className, String typedefName) { + public Shape(Namespace parent, String className, String typedefName, String description) { this.parent = parent; this.className = className; this.typedefName = typedefName; - } - - public Type getType() { - return Type.builder().pkg(getPackageName()).name(className).build(); - } - - public Namespace getParent() { - return this.parent; + this.description = description; } public String getPackageName() { @@ -44,10 +41,38 @@ public String getClassName() { return this.className; } + public JavaClassKind getClassKind() { + return JavaClassKind.Class; + } + public String getTypedefName() { return this.typedefName; } + public String getDescription() { + return this.description; + } + + public Collection getAnnotations() { + return Collections.emptyList(); + } + + public Type getExtendsType() { + return null; + } + + public Collection getImplementsTypes() { + return Collections.emptyList(); + } + + public Type getType() { + return Type.builder().pkg(getPackageName()).name(className).build(); + } + + public Namespace getParent() { + return this.parent; + } + public void render(ShapeRenderingContext ctx) throws RenderException { var outFile = ctx.getOutputFile(className + ".java"); LOGGER.info("Rendering: {}", outFile); diff --git a/java-codegen/src/main/java/org/opensearch/client/codegen/model/SpecTransformer.java b/java-codegen/src/main/java/org/opensearch/client/codegen/model/SpecTransformer.java index a049ca5ea4..e228764097 100644 --- a/java-codegen/src/main/java/org/opensearch/client/codegen/model/SpecTransformer.java +++ b/java-codegen/src/main/java/org/opensearch/client/codegen/model/SpecTransformer.java @@ -231,16 +231,24 @@ private void visit(Namespace parent, String className, String typedefName, OpenA Shape shape; + var description = schema.getDescription().orElse(null); + if (schema.isArray()) { - shape = new ArrayShape(parent, className, mapType(schema), typedefName); + shape = new ArrayShape(parent, className, mapType(schema), typedefName, description); } else if (schema.isObject() || schema.hasAllOf() || schema.equals(OpenApiSchema.ANONYMOUS_OBJECT)) { - var objShape = new ObjectShape(parent, className, typedefName); + var objShape = new ObjectShape(parent, className, typedefName, description); visitInto(schema, objShape); shape = objShape; } else if (schema.isString() && schema.hasEnums()) { - shape = new EnumShape(parent, className, Lists.map(schema.getEnums().orElseThrow(), EnumShape.Variant::new), typedefName); + shape = new EnumShape( + parent, + className, + Lists.map(schema.getEnums().orElseThrow(), EnumShape.Variant::new), + typedefName, + description + ); } else if (schema.hasOneOf()) { - var taggedUnion = new TaggedUnionShape(parent, className, typedefName); + var taggedUnion = new TaggedUnionShape(parent, className, typedefName, description); schema.getOneOf().orElseThrow().forEach(s -> { var title = s.getTitle() .orElseThrow(() -> new IllegalStateException("oneOf variant [" + s.getPointer() + "] is missing a `title` tag")); diff --git a/java-codegen/src/main/java/org/opensearch/client/codegen/model/TaggedUnionShape.java b/java-codegen/src/main/java/org/opensearch/client/codegen/model/TaggedUnionShape.java index 2eb78fcc56..85e96761a0 100644 --- a/java-codegen/src/main/java/org/opensearch/client/codegen/model/TaggedUnionShape.java +++ b/java-codegen/src/main/java/org/opensearch/client/codegen/model/TaggedUnionShape.java @@ -16,8 +16,8 @@ public class TaggedUnionShape extends ObjectShape { private final List variants = new ArrayList<>(); - public TaggedUnionShape(Namespace parent, String className, String typedefName) { - super(parent, className, typedefName); + public TaggedUnionShape(Namespace parent, String className, String typedefName, String description) { + super(parent, className, typedefName, description); } public void addVariant(String name, Type type) { diff --git a/java-codegen/src/main/java/org/opensearch/client/codegen/model/Types.java b/java-codegen/src/main/java/org/opensearch/client/codegen/model/Types.java index 5451ae949f..7dc66ca479 100644 --- a/java-codegen/src/main/java/org/opensearch/client/codegen/model/Types.java +++ b/java-codegen/src/main/java/org/opensearch/client/codegen/model/Types.java @@ -112,6 +112,7 @@ public static final class Javax { public static final class Annotation { public static final String PACKAGE = Javax.PACKAGE + ".annotation"; + public static final Type Generated = Type.builder().pkg(PACKAGE).name("Generated").build(); public static final Type Nullable = Type.builder().pkg(PACKAGE).name("Nullable").build(); } } @@ -119,6 +120,10 @@ public static final class Annotation { public static final class Client { public static final String PACKAGE = "org.opensearch.client"; + public static Type ApiClient(Type transport, Type client) { + return ApiClient.withGenericArgs(transport, client); + } + public static final Type ApiClient = Type.builder().pkg(PACKAGE).name("ApiClient").build(); public static final class Json { diff --git a/java-codegen/src/main/java/org/opensearch/client/codegen/utils/JavaClassKind.java b/java-codegen/src/main/java/org/opensearch/client/codegen/utils/JavaClassKind.java new file mode 100644 index 0000000000..2e587790c2 --- /dev/null +++ b/java-codegen/src/main/java/org/opensearch/client/codegen/utils/JavaClassKind.java @@ -0,0 +1,33 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.client.codegen.utils; + +import java.util.Map; +import javax.annotation.Nonnull; + +public enum JavaClassKind { + Class, + Enum; + + private static final Map VALUES = Maps.createLookupOf(values(), JavaClassKind::toString); + + @Nonnull + public static JavaClassKind from(@Nonnull String kind) { + var value = VALUES.get(Strings.requireNonBlank(kind, "kind must not be blank")); + if (value == null) { + throw new IllegalArgumentException("Unknown kind: " + kind); + } + return value; + } + + @Override + public String toString() { + return name().toLowerCase(); + } +} diff --git a/java-codegen/src/main/resources/org/opensearch/client/codegen/templates/ArrayShape.mustache b/java-codegen/src/main/resources/org/opensearch/client/codegen/templates/ArrayShape.mustache index 86acdf44f5..c3fb5ff0d0 100644 --- a/java-codegen/src/main/resources/org/opensearch/client/codegen/templates/ArrayShape.mustache +++ b/java-codegen/src/main/resources/org/opensearch/client/codegen/templates/ArrayShape.mustache @@ -1,4 +1,4 @@ -{{>ObjectShape/ClassDeclaration}} { +{{>Partials/ClassDeclaration}} { {{>ObjectShape/Fields}} // --------------------------------------------------------------------------------------------- diff --git a/java-codegen/src/main/resources/org/opensearch/client/codegen/templates/Client.mustache b/java-codegen/src/main/resources/org/opensearch/client/codegen/templates/Client.mustache index 134ea1d904..0680d188c2 100644 --- a/java-codegen/src/main/resources/org/opensearch/client/codegen/templates/Client.mustache +++ b/java-codegen/src/main/resources/org/opensearch/client/codegen/templates/Client.mustache @@ -1,4 +1,4 @@ -public class {{className}} extends {{TYPES.Client.ApiClient}}<{{TYPES.Client.Transport.OpenSearchTransport}}, {{className}}> { +{{>Partials/ClassDeclaration}} { public {{className}}({{TYPES.Client.Transport.OpenSearchTransport}} transport) { super(transport, null); } diff --git a/java-codegen/src/main/resources/org/opensearch/client/codegen/templates/EnumShape.mustache b/java-codegen/src/main/resources/org/opensearch/client/codegen/templates/EnumShape.mustache index 129fe297a7..78c5bfc7fa 100644 --- a/java-codegen/src/main/resources/org/opensearch/client/codegen/templates/EnumShape.mustache +++ b/java-codegen/src/main/resources/org/opensearch/client/codegen/templates/EnumShape.mustache @@ -1,12 +1,4 @@ -// typedef: {{typedefName}} - -{{#description}} -/** - * {{.}} - */ -{{/description}} -@{{TYPES.Client.Json.JsonpDeserializable}} -public enum {{className}} implements {{TYPES.Client.Json.JsonEnum}} { +{{>Partials/ClassDeclaration}} { {{#variants}} {{name}}({{#quoted}}{{wireName}}{{/quoted}}){{^-last}},{{/-last}} {{/variants}} diff --git a/java-codegen/src/main/resources/org/opensearch/client/codegen/templates/ObjectShape.mustache b/java-codegen/src/main/resources/org/opensearch/client/codegen/templates/ObjectShape.mustache index b6f252f6cd..3d4c1b94ce 100644 --- a/java-codegen/src/main/resources/org/opensearch/client/codegen/templates/ObjectShape.mustache +++ b/java-codegen/src/main/resources/org/opensearch/client/codegen/templates/ObjectShape.mustache @@ -1,4 +1,4 @@ -{{>ObjectShape/ClassDeclaration}} { +{{>Partials/ClassDeclaration}} { {{>ObjectShape/Fields}} // --------------------------------------------------------------------------------------------- diff --git a/java-codegen/src/main/resources/org/opensearch/client/codegen/templates/ObjectShape/ClassDeclaration.mustache b/java-codegen/src/main/resources/org/opensearch/client/codegen/templates/ObjectShape/ClassDeclaration.mustache deleted file mode 100644 index f08d1623b8..0000000000 --- a/java-codegen/src/main/resources/org/opensearch/client/codegen/templates/ObjectShape/ClassDeclaration.mustache +++ /dev/null @@ -1,11 +0,0 @@ -// typedef: {{typedefName}} - -{{#description}} -/** -* {{.}} -*/ -{{/description}} -{{#annotations}} -@{{.}} -{{/annotations}} -public class {{className}}{{#extendsType}} extends {{.}}{{/extendsType}}{{#implementsTypes}}{{#-first}} implements{{/-first}} {{.}}{{^-last}},{{/-last}}{{/implementsTypes}} diff --git a/java-codegen/src/main/resources/org/opensearch/client/codegen/templates/Partials/ClassDeclaration.mustache b/java-codegen/src/main/resources/org/opensearch/client/codegen/templates/Partials/ClassDeclaration.mustache new file mode 100644 index 0000000000..ee29f712fb --- /dev/null +++ b/java-codegen/src/main/resources/org/opensearch/client/codegen/templates/Partials/ClassDeclaration.mustache @@ -0,0 +1,14 @@ +{{#typedefName}} +// typedef: {{.}} + +{{/typedefName}} +{{#description}} +/** +* {{.}} +*/ +{{/description}} +{{#annotations}} +@{{.}} +{{/annotations}} +@{{TYPES.Javax.Annotation.Generated}}("org.opensearch.client.codegen.CodeGenerator") +public {{classKind}} {{className}}{{#extendsType}} extends {{.}}{{/extendsType}}{{#implementsTypes}}{{#-first}} implements{{/-first}} {{.}}{{^-last}},{{/-last}}{{/implementsTypes}} diff --git a/java-codegen/src/main/resources/org/opensearch/client/codegen/templates/RequestShape.mustache b/java-codegen/src/main/resources/org/opensearch/client/codegen/templates/RequestShape.mustache index 9137af0758..99a3039a73 100644 --- a/java-codegen/src/main/resources/org/opensearch/client/codegen/templates/RequestShape.mustache +++ b/java-codegen/src/main/resources/org/opensearch/client/codegen/templates/RequestShape.mustache @@ -1,4 +1,4 @@ -{{>ObjectShape/ClassDeclaration}} { +{{>Partials/ClassDeclaration}} { {{#canBeSingleton}} public {{className}}() {} diff --git a/java-codegen/src/main/resources/org/opensearch/client/codegen/templates/TaggedUnionShape.mustache b/java-codegen/src/main/resources/org/opensearch/client/codegen/templates/TaggedUnionShape.mustache index 2615d80e61..b324c9a454 100644 --- a/java-codegen/src/main/resources/org/opensearch/client/codegen/templates/TaggedUnionShape.mustache +++ b/java-codegen/src/main/resources/org/opensearch/client/codegen/templates/TaggedUnionShape.mustache @@ -1,4 +1,4 @@ -{{>ObjectShape/ClassDeclaration}} { +{{>Partials/ClassDeclaration}} { public enum Kind { {{#variants}}{{#pascalCase}}{{name}}{{/pascalCase}}{{^-last}},{{/-last}}{{/variants}} }