Skip to content

Commit c6fa95a

Browse files
authored
Merge pull request #12100 from swagger-api/issue-11234
Kotlin Server Generator Update
2 parents 3954108 + 399ae9d commit c6fa95a

File tree

81 files changed

+1778
-1767
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

81 files changed

+1778
-1767
lines changed

modules/swagger-codegen/src/main/java/io/swagger/codegen/DefaultCodegen.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,18 @@
22

33
import javax.annotation.Nullable;
44
import java.io.File;
5+
import java.io.IOException;
56
import java.io.UnsupportedEncodingException;
7+
import java.io.Writer;
68
import java.net.URLDecoder;
79
import java.nio.charset.StandardCharsets;
810
import java.util.*;
911
import java.util.Map.Entry;
1012
import java.util.regex.Matcher;
1113
import java.util.regex.Pattern;
1214

15+
import com.samskivert.mustache.Mustache;
16+
import com.samskivert.mustache.Template;
1317
import io.swagger.models.properties.UntypedProperty;
1418
import org.apache.commons.lang3.ObjectUtils;
1519
import org.apache.commons.lang3.StringEscapeUtils;
@@ -174,6 +178,13 @@ public void processOpts() {
174178
} else {
175179
setIgnoreImportMapping(defaultIgnoreImportMappingOption());
176180
}
181+
182+
additionalProperties.put("toLowerCase", new Mustache.Lambda() {
183+
@Override
184+
public void execute(Template.Fragment fragment, Writer writer) throws IOException {
185+
writer.write(fragment.execute().toLowerCase());
186+
}
187+
});
177188
}
178189

179190
// override with any special post-processing for all models
@@ -3183,7 +3194,7 @@ public void addOperationToGroup(String tag, String resourcePath, Operation opera
31833194
co.baseName = tag;
31843195
}
31853196

3186-
private void addParentContainer(CodegenModel m, String name, Property property) {
3197+
protected void addParentContainer(CodegenModel m, String name, Property property) {
31873198
m.parentContainer = fromProperty(name, property);
31883199
addImport(m, m.parentContainer.complexType);
31893200
m.parent = toInstantiationType(property);
@@ -3381,7 +3392,7 @@ private void addVars(CodegenModel m, List<CodegenProperty> vars, Map<String, Pro
33813392
* @param allDefinitions The complete set of model definitions.
33823393
* @return A mapping from model name to type alias
33833394
*/
3384-
private static Map<String, String> getAllAliases(Map<String, Model> allDefinitions) {
3395+
protected Map<String, String> getAllAliases(Map<String, Model> allDefinitions) {
33853396
Map<String, String> aliases = new HashMap<>();
33863397
if (allDefinitions != null) {
33873398
for (Map.Entry<String, Model> entry : allDefinitions.entrySet()) {

modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/AbstractKotlinCodegen.java

Lines changed: 76 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,18 @@
44
import io.swagger.codegen.CodegenConfig;
55
import io.swagger.codegen.CodegenConstants;
66
import io.swagger.codegen.CodegenConstants.ENUM_PROPERTY_NAMING_TYPE;
7+
import io.swagger.codegen.CodegenModel;
8+
import io.swagger.codegen.CodegenProperty;
79
import io.swagger.codegen.DefaultCodegen;
810
import io.swagger.codegen.utils.EnumPropertyNamingUtils;
11+
import io.swagger.models.ArrayModel;
12+
import io.swagger.models.BooleanValueModel;
13+
import io.swagger.models.Model;
14+
import io.swagger.models.ModelImpl;
915
import io.swagger.models.properties.ArrayProperty;
1016
import io.swagger.models.properties.MapProperty;
1117
import io.swagger.models.properties.Property;
18+
import org.apache.commons.lang3.StringUtils;
1219
import org.slf4j.Logger;
1320
import org.slf4j.LoggerFactory;
1421

@@ -154,6 +161,7 @@ public AbstractKotlinCodegen() {
154161
typeMapping.put("binary", "kotlin.Array<kotlin.Byte>");
155162
typeMapping.put("Date", "java.time.LocalDateTime");
156163
typeMapping.put("DateTime", "java.time.LocalDateTime");
164+
typeMapping.put("ByteArray", "kotlin.ByteArray");
157165

158166
instantiationTypes.put("array", "arrayOf");
159167
instantiationTypes.put("list", "arrayOf");
@@ -353,6 +361,16 @@ public void setSourceFolder(String sourceFolder) {
353361
this.sourceFolder = sourceFolder;
354362
}
355363

364+
@Override
365+
public String toVarName(String name) {
366+
return super.toVarName(sanitizeKotlinSpecificNames(name));
367+
}
368+
369+
@Override
370+
public String toEnumName(CodegenProperty property) {
371+
return StringUtils.capitalize(property.name);
372+
}
373+
356374
/**
357375
* Return the sanitized variable name for enum
358376
*
@@ -370,13 +388,9 @@ public String toEnumVarName(String value, String datatype) {
370388
modified = sanitizeKotlinSpecificNames(modified);
371389
}
372390

373-
if (getEnumPropertyNaming() == ENUM_PROPERTY_NAMING_TYPE.original) {
374-
// NOTE: This is provided as a last-case allowance, but will still result in reserved words being escaped.
375-
modified = value;
376-
}
377-
modified = EnumPropertyNamingUtils.applyEnumPropertyCapitalisation(modified, getEnumPropertyNaming());
391+
modified = modified.toUpperCase();
378392

379-
if (reservedWords.contains(modified)) {
393+
if (isReservedWord(modified)) {
380394
return escapeReservedWord(modified);
381395
}
382396

@@ -388,6 +402,9 @@ public String toEnumValue(String value, String datatype) {
388402
if (isPrimivite(datatype)) {
389403
return value;
390404
}
405+
if ("java.math.BigDecimal".equalsIgnoreCase(datatype)) {
406+
return "java.math.BigDecimal(\"" + value + "\")";
407+
}
391408
return super.toEnumValue(value, datatype);
392409
}
393410

@@ -435,7 +452,7 @@ public String toModelImport(String name) {
435452
* @return capitalized model name
436453
*/
437454
@Override
438-
public String toModelName(final String name) {
455+
public String toModelName(final String name) {
439456
// Allow for explicitly configured kotlin.* and java.* types
440457
if (name.startsWith("kotlin.") || name.startsWith("java.")) {
441458
return name;
@@ -449,14 +466,19 @@ public String toModelName(final String name) {
449466
String modifiedName = name.replaceAll("\\.", "");
450467
modifiedName = sanitizeKotlinSpecificNames(modifiedName);
451468

452-
// Camelize name of nested properties
453-
modifiedName = camelize(modifiedName);
469+
modifiedName = titleCase(modifiedName);
470+
471+
if (modifiedName.equalsIgnoreCase("Companion")) {
472+
modifiedName = "_" + modifiedName;
473+
}
454474

455-
if (reservedWords.contains(modifiedName)) {
456-
modifiedName = escapeReservedWord(modifiedName);
475+
if (modifiedName.matches("^\\d.*")) {
476+
final String modelName = "Model" + modifiedName; // e.g. 200Response => Model200Response (after camelize)
477+
LOGGER.warn(name + " (model name starts with number) cannot be used as model name. Renamed to " + modelName);
478+
return modelName;
457479
}
458480

459-
return titleCase(modifiedName);
481+
return modifiedName;
460482
}
461483

462484
@Override
@@ -490,7 +512,8 @@ private String getArrayTypeDeclaration(ArrayProperty arr) {
490512
* @return sanitized string
491513
*/
492514
private String sanitizeKotlinSpecificNames(final String name) {
493-
String word = name;
515+
String word = removeNonNameElementToCamelCase(name);
516+
494517
for (Map.Entry<String, String> specialCharacters : specialCharReplacements.entrySet()) {
495518
// Underscore is the only special character we'll allow
496519
if (!specialCharacters.getKey().equals("_")) {
@@ -530,4 +553,44 @@ protected boolean needToImport(String type) {
530553
boolean imports = !type.startsWith("kotlin.") && !type.startsWith("java.") && !defaultIncludes.contains(type) && !languageSpecificPrimitives.contains(type);
531554
return imports;
532555
}
556+
557+
@Override
558+
protected Map<String, String> getAllAliases(Map<String, Model> allDefinitions) {
559+
Map<String, String> aliases = new HashMap<>();
560+
if (allDefinitions != null) {
561+
for (Map.Entry<String, Model> entry : allDefinitions.entrySet()) {
562+
String swaggerName = entry.getKey();
563+
Model m = entry.getValue();
564+
if (m instanceof ModelImpl) {
565+
ModelImpl impl = (ModelImpl) m;
566+
if (impl.getType() != null &&
567+
!impl.getType().equals("object") &&
568+
(impl.getEnum() == null)) {
569+
aliases.put(swaggerName, impl.getType());
570+
}
571+
}
572+
if (m instanceof ArrayModel) {
573+
ArrayModel impl = (ArrayModel) m;
574+
aliases.put(swaggerName, impl.getType());
575+
}
576+
}
577+
}
578+
return aliases;
579+
}
580+
581+
@Override
582+
protected void addParentContainer(CodegenModel m, String name, Property property) {
583+
m.parentContainer = fromProperty(name, property);
584+
addImport(m, m.parentContainer.complexType);
585+
m.parent = toInstantiationType(property);
586+
final String containerType = m.parentContainer.containerType;
587+
final String instantiationType = instantiationTypes.get(containerType);
588+
if (instantiationType != null && !m.isArrayModel) {
589+
addImport(m, instantiationType);
590+
}
591+
final String mappedType = typeMapping.get(containerType);
592+
if (mappedType != null) {
593+
addImport(m, mappedType);
594+
}
595+
}
533596
}

modules/swagger-codegen/src/main/resources/kotlin-client/api.mustache

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,53 @@ class {{classname}}(basePath: kotlin.String = "{{{basePath}}}") : ApiClient(base
2222
@Suppress("UNCHECKED_CAST"){{/returnType}}
2323
fun {{operationId}}({{#allParams}}{{paramName}}: {{{dataType}}}{{#hasMore}}, {{/hasMore}}{{/allParams}}) : {{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}Unit{{/returnType}} {
2424
val localVariableBody: kotlin.Any? = {{#hasBodyParam}}{{#bodyParams}}{{paramName}}{{/bodyParams}}{{/hasBodyParam}}{{^hasBodyParam}}{{^hasFormParams}}null{{/hasFormParams}}{{#hasFormParams}}mapOf({{#formParams}}"{{{baseName}}}" to {{paramName}}{{#hasMore}}, {{/hasMore}}{{/formParams}}){{/hasFormParams}}{{/hasBodyParam}}
25-
val localVariableQuery: MultiValueMap = {{^hasQueryParams}}mapOf(){{/hasQueryParams}}{{#hasQueryParams}}mapOf({{#queryParams}}"{{baseName}}" to {{#isContainer}}toMultiValue({{paramName}}.toList(), "{{collectionFormat}}"){{/isContainer}}{{^isContainer}}listOf("${{paramName}}"){{/isContainer}}{{#hasMore}}, {{/hasMore}}{{/queryParams}}){{/hasQueryParams}}
26-
27-
val contentHeaders: kotlin.collections.Map<kotlin.String,kotlin.String> = mapOf({{#hasFormParams}}"Content-Type" to "multipart/form-data"{{/hasFormParams}})
28-
val acceptsHeaders: kotlin.collections.Map<kotlin.String,kotlin.String> = mapOf({{#hasProduces}}"Accept" to "{{#produces}}{{#isContainer}}{{mediaType}}.joinToString(separator = collectionDelimiter("{{collectionFormat}}"){{/isContainer}}{{^isContainer}}{{mediaType}}{{/isContainer}}{{#hasMore}}, {{/hasMore}}{{/produces}}"{{/hasProduces}})
29-
val localVariableHeaders: kotlin.collections.MutableMap<kotlin.String,kotlin.String> = mutableMapOf({{#hasHeaderParams}}{{#headerParams}}"{{baseName}}" to {{#isContainer}}{{paramName}}.joinToString(separator = collectionDelimiter("{{collectionFormat}}"){{/isContainer}}{{^isContainer}}{{paramName}}{{/isContainer}}{{#hasMore}}, {{/hasMore}}{{/headerParams}}{{/hasHeaderParams}})
30-
localVariableHeaders.putAll(contentHeaders)
31-
localVariableHeaders.putAll(acceptsHeaders)
25+
{{#hasQueryParams}}
26+
val localVariableQuery: MultiValueMap = mutableMapOf<kotlin.String, kotlin.collections.List<kotlin.String>>().apply {
27+
{{#queryParams}}
28+
{{^required}}
29+
if ({{{paramName}}} != null) {
30+
put("{{baseName}}", {{#isContainer}}toMultiValue({{{paramName}}}.toList(), "{{collectionFormat}}"){{/isContainer}}{{^isContainer}}listOf({{#isDateTime}}parseDateToQueryString({{{paramName}}}){{/isDateTime}}{{#isDate}}parseDateToQueryString({{{paramName}}}){{/isDate}}{{^isDateTime}}{{^isDate}}{{{paramName}}}.toString(){{/isDate}}{{/isDateTime}}){{/isContainer}})
31+
}
32+
{{/required}}
33+
{{#required}}
34+
put("{{baseName}}", {{#isContainer}}toMultiValue({{{paramName}}}.toList(), "{{collectionFormat}}"){{/isContainer}}{{^isContainer}}listOf({{#isDateTime}}parseDateToQueryString({{{paramName}}}){{/isDateTime}}{{#isDate}}parseDateToQueryString({{{paramName}}}){{/isDate}}{{^isDateTime}}{{^isDate}}{{{paramName}}}.toString(){{/isDate}}{{/isDateTime}}){{/isContainer}})
35+
{{/required}}
36+
{{/queryParams}}
37+
}
38+
{{/hasQueryParams}}
3239

40+
{{#hasHeaderParams}}
41+
val localVariableHeaders: MutableMap<String, String> = mutableMapOf({{#hasFormParams}}"Content-Type" to {{^consumes}}"multipart/form-data"{{/consumes}}{{#consumes.0}}"{{{mediaType}}}"{{/consumes.0}}{{/hasFormParams}})
42+
{{#headerParams}}
43+
{{{paramName}}}{{^required}}?{{/required}}.apply {
44+
localVariableHeaders["{{baseName}}"] = {{#isContainer}}this.joinToString(separator = collectionDelimiter("{{collectionFormat}}")){{/isContainer}}{{^isContainer}}this.toString(){{/isContainer}}
45+
}
46+
{{/headerParams}}
47+
{{^hasFormParams}}{{#hasConsumes}}{{#consumes}}localVariableHeaders["Content-Type"] = "{{{mediaType}}}"
48+
{{/consumes}}{{/hasConsumes}}{{/hasFormParams}}{{#hasProduces}}localVariableHeaders["Accept"] = "{{#produces}}{{{mediaType}}}{{^-last}}, {{/-last}}{{/produces}}"{{/hasProduces}}
49+
{{/hasHeaderParams}}
50+
{{^hasHeaderParams}}
51+
{{#hasFormParams}}
52+
val localVariableHeaders: MutableMap<String, String> = mutableMapOf("Content-Type" to {{^consumes}}"multipart/form-data"{{/consumes}}{{#consumes.0}}"{{{mediaType}}}"{{/consumes.0}})
53+
{{#hasConsumes}}{{#consumes}}localVariableHeaders["Content-Type"] = "{{{mediaType}}}"
54+
{{/consumes}}{{/hasConsumes}}{{#hasProduces}}localVariableHeaders["Accept"] = "{{#produces}}{{{mediaType}}}{{^-last}}, {{/-last}}{{/produces}}"{{/hasProduces}}
55+
{{/hasFormParams}}
56+
{{/hasHeaderParams}}
57+
3358
val localVariableConfig = RequestConfig(
3459
RequestMethod.{{httpMethod}},
3560
"{{path}}"{{#pathParams}}.replace("{"+"{{baseName}}"+"}", "${{paramName}}"){{/pathParams}},
61+
{{#hasQueryParams}}
3662
query = localVariableQuery,
63+
{{/hasQueryParams}}
64+
{{#hasHeaderParams}}
65+
headers = localVariableHeaders
66+
{{/hasHeaderParams}}
67+
{{^hasHeaderParams}}
68+
{{#hasFormParams}}
3769
headers = localVariableHeaders
70+
{{/hasFormParams}}
71+
{{/hasHeaderParams}}
3872
)
3973
val response = request<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}Unit{{/returnType}}>(
4074
localVariableConfig,
Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
group '{{groupId}}'
22
version '{{artifactVersion}}'
33

4-
task wrapper(type: Wrapper) {
5-
gradleVersion = '3.3'
4+
wrapper {
5+
gradleVersion = '7.5'
66
distributionUrl = "https://services.gradle.org/distributions/gradle-$gradleVersion-all.zip"
77
}
88

99
buildscript {
10-
ext.kotlin_version = '1.1.2'
10+
ext.kotlin_version = '1.8.0'
1111
1212
repositories {
13-
mavenCentral()
13+
maven { url "https://repo1.maven.org/maven2" }
1414
}
1515
dependencies {
1616
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
@@ -20,14 +20,14 @@ buildscript {
2020
apply plugin: 'kotlin'
2121

2222
repositories {
23-
mavenCentral()
23+
maven { url "https://repo1.maven.org/maven2" }
2424
}
2525

2626
dependencies {
27-
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
28-
compile "com.squareup.moshi:moshi-kotlin:1.5.0"
29-
compile "com.squareup.moshi:moshi-adapters:1.5.0"
30-
compile "com.squareup.okhttp3:okhttp:3.8.0"
31-
compile "org.threeten:threetenbp:1.3.6"
32-
testCompile "io.kotlintest:kotlintest:2.0.2"
33-
}
27+
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
28+
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
29+
implementation "com.squareup.moshi:moshi-kotlin:1.11.0"
30+
implementation "com.squareup.moshi:moshi-adapters:1.11.0"
31+
implementation "com.squareup.okhttp3:okhttp:4.9.0"
32+
testImplementation "io.kotlintest:kotlintest:2.0.7"
33+
}
Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,37 @@
1-
{{#hasEnums}}
2-
import com.squareup.moshi.Json
3-
{{/hasEnums}}
41
/**
52
* {{{description}}}
63
{{#vars}}
74
* @param {{name}} {{{description}}}
85
{{/vars}}
96
*/
107
data class {{classname}} (
11-
{{#requiredVars}}
12-
{{>data_class_req_var}}{{^-last}},
13-
{{/-last}}{{/requiredVars}}{{#hasRequired}}{{#hasOptional}},
14-
{{/hasOptional}}{{/hasRequired}}{{#optionalVars}}{{>data_class_opt_var}}{{^-last}},
15-
{{/-last}}{{/optionalVars}}
8+
{{#vars}}
9+
{{#required}}
10+
{{>data_class_req_var}}{{^@last}},{{/@last}}
11+
{{/required}}
12+
{{^required}}
13+
{{>data_class_opt_var}}{{^@last}},{{/@last}}
14+
{{/required}}
15+
{{/vars}}
1616
) {
17-
{{#hasEnums}}{{#vars}}{{#isEnum}}
17+
{{#hasEnums}}{{#vars}}
18+
19+
{{#isEnum}}
1820
/**
1921
* {{{description}}}
2022
* Values: {{#allowableValues}}{{#enumVars}}{{&name}}{{^-last}},{{/-last}}{{/enumVars}}{{/allowableValues}}
2123
*/
22-
enum class {{nameInCamelCase}}(val value: {{datatype}}){
24+
{{#items}}
25+
enum class {{{datatypeWithEnum}}}(val value: {{{datatype}}}{{#isNullable}}?{{/isNullable}}){
26+
{{/items}}
27+
{{^items}}
28+
enum class {{nameInCamelCase}}(val value: {{{datatype}}}){
29+
{{/items}}
2330
{{#allowableValues}}{{#enumVars}}
24-
@Json(name = {{{value}}}) {{&name}}({{{value}}}){{^-last}},{{/-last}}{{#-last}};{{/-last}}
31+
{{&name}}({{{value}}}){{^-last}},{{/-last}}{{#-last}};{{/-last}}
2532
{{/enumVars}}{{/allowableValues}}
2633
}
27-
{{/isEnum}}{{/vars}}{{/hasEnums}}
34+
35+
{{/isEnum}}
36+
{{/vars}}{{/hasEnums}}
2837
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{{#description}}
22
/* {{{description}}} */
33
{{/description}}
4-
val {{{name}}}: {{#isEnum}}{{classname}}.{{nameInCamelCase}}{{/isEnum}}{{^isEnum}}{{{datatype}}}{{/isEnum}}? = {{#defaultvalue}}{{defaultvalue}}{{/defaultvalue}}{{^defaultvalue}}null{{/defaultvalue}}
4+
val {{{name}}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{datatype}}}{{/isEnum}}? = {{#defaultvalue}}{{defaultvalue}}{{/defaultvalue}}{{^defaultvalue}}null{{/defaultvalue}}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{{#description}}
22
/* {{{description}}} */
33
{{/description}}
4-
val {{{name}}}: {{#isEnum}}{{classname}}.{{nameInCamelCase}}{{/isEnum}}{{^isEnum}}{{{datatype}}}{{/isEnum}}
4+
val {{{name}}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{datatype}}}{{/isEnum}}
Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
import com.squareup.moshi.Json
2-
31
/**
42
* {{{description}}}
53
* Values: {{#allowableValues}}{{#enumVars}}{{&name}}{{^-last}},{{/-last}}{{/enumVars}}{{/allowableValues}}
64
*/
7-
enum class {{classname}}(val value: {{dataType}}){
5+
enum class {{classname}}(val value: {{dataType}}{{#isNullable}}?{{/isNullable}}){
86
{{#allowableValues}}{{#enumVars}}
9-
@Json(name = {{{value}}}) {{&name}}({{{value}}}){{^-last}},{{/-last}}{{#-last}};{{/-last}}
7+
{{&name}}({{{value}}}){{^-last}},{{/-last}}{{#-last}};{{/-last}}
108
{{/enumVars}}{{/allowableValues}}
11-
}
9+
}

0 commit comments

Comments
 (0)