From 8fa4dd99a61bdd07333b7a4adff513a528a03fbf Mon Sep 17 00:00:00 2001 From: tcslater Date: Wed, 21 Feb 2018 08:43:32 +1100 Subject: [PATCH 01/34] Migrating kotlin generator into swagger-codegen-genertors repo for 3.0.0 [WIP] --- .../kotlin/AbstractKotlinCodegen.java | 553 ++++++++++++++++++ .../languages/kotlin/KotlinClientCodegen.java | 118 ++++ .../languages/kotlin/KotlinServerCodegen.java | 224 +++++++ .../v2/kotlin-client/README.mustache | 85 +++ .../resources/v2/kotlin-client/api.mustache | 50 ++ .../v2/kotlin-client/api_doc.mustache | 65 ++ .../v2/kotlin-client/build.gradle.mustache | 33 ++ .../v2/kotlin-client/class_doc.mustache | 15 + .../v2/kotlin-client/data_class.mustache | 25 + .../kotlin-client/data_class_opt_var.mustache | 4 + .../kotlin-client/data_class_req_var.mustache | 4 + .../v2/kotlin-client/enum_class.mustache | 9 + .../v2/kotlin-client/enum_doc.mustache | 7 + .../ApiAbstractions.kt.mustache | 20 + .../infrastructure/ApiClient.kt.mustache | 128 ++++ .../ApiInfrastructureResponse.kt.mustache | 40 ++ .../ApplicationDelegates.kt.mustache | 29 + .../infrastructure/Errors.kt.mustache | 42 ++ .../infrastructure/RequestConfig.kt.mustache | 15 + .../infrastructure/RequestMethod.kt.mustache | 8 + .../ResponseExtensions.kt.mustache | 23 + .../infrastructure/Serializer.kt.mustache | 14 + .../v2/kotlin-client/licenseInfo.mustache | 11 + .../resources/v2/kotlin-client/model.mustache | 14 + .../v2/kotlin-client/model_doc.mustache | 3 + .../v2/kotlin-client/settings.gradle.mustache | 1 + .../v2/kotlin-server/README.mustache | 84 +++ .../v2/kotlin-server/api_doc.mustache | 65 ++ .../v2/kotlin-server/class_doc.mustache | 15 + .../v2/kotlin-server/data_class.mustache | 25 + .../kotlin-server/data_class_opt_var.mustache | 4 + .../kotlin-server/data_class_req_var.mustache | 4 + .../v2/kotlin-server/enum_class.mustache | 9 + .../v2/kotlin-server/enum_doc.mustache | 7 + .../libraries/ktor/ApiKeyAuth.kt.mustache | 57 ++ .../libraries/ktor/AppMain.kt.mustache | 74 +++ .../libraries/ktor/Configuration.kt.mustache | 108 ++++ .../libraries/ktor/Dockerfile.mustache | 7 + .../libraries/ktor/Paths.kt.mustache | 43 ++ .../libraries/ktor/README.mustache | 101 ++++ .../libraries/ktor/_api_body.mustache | 25 + .../libraries/ktor/_principal.mustache | 11 + .../libraries/ktor/_response.mustache | 8 + .../kotlin-server/libraries/ktor/api.mustache | 114 ++++ .../libraries/ktor/application.conf.mustache | 27 + .../libraries/ktor/build.gradle.mustache | 74 +++ .../libraries/ktor/gradle.properties | 1 + .../libraries/ktor/licenseInfo.mustache | 11 + .../kotlin-server/libraries/ktor/logback.xml | 15 + .../v2/kotlin-server/licenseInfo.mustache | 11 + .../resources/v2/kotlin-server/model.mustache | 11 + .../v2/kotlin-server/model_doc.mustache | 3 + .../v2/kotlin-server/settings.gradle.mustache | 1 + .../kotlin/KotlinClientCodegenModelTest.java | 276 +++++++++ .../KotlinClientCodegenOptionsTest.java | 45 ++ .../KotlinClientCodegenOptionsProvider.java | 42 ++ 56 files changed, 2818 insertions(+) create mode 100644 src/main/java/io/swagger/codegen/languages/kotlin/AbstractKotlinCodegen.java create mode 100644 src/main/java/io/swagger/codegen/languages/kotlin/KotlinClientCodegen.java create mode 100644 src/main/java/io/swagger/codegen/languages/kotlin/KotlinServerCodegen.java create mode 100644 src/main/resources/v2/kotlin-client/README.mustache create mode 100644 src/main/resources/v2/kotlin-client/api.mustache create mode 100644 src/main/resources/v2/kotlin-client/api_doc.mustache create mode 100644 src/main/resources/v2/kotlin-client/build.gradle.mustache create mode 100644 src/main/resources/v2/kotlin-client/class_doc.mustache create mode 100644 src/main/resources/v2/kotlin-client/data_class.mustache create mode 100644 src/main/resources/v2/kotlin-client/data_class_opt_var.mustache create mode 100644 src/main/resources/v2/kotlin-client/data_class_req_var.mustache create mode 100644 src/main/resources/v2/kotlin-client/enum_class.mustache create mode 100644 src/main/resources/v2/kotlin-client/enum_doc.mustache create mode 100644 src/main/resources/v2/kotlin-client/infrastructure/ApiAbstractions.kt.mustache create mode 100644 src/main/resources/v2/kotlin-client/infrastructure/ApiClient.kt.mustache create mode 100644 src/main/resources/v2/kotlin-client/infrastructure/ApiInfrastructureResponse.kt.mustache create mode 100644 src/main/resources/v2/kotlin-client/infrastructure/ApplicationDelegates.kt.mustache create mode 100644 src/main/resources/v2/kotlin-client/infrastructure/Errors.kt.mustache create mode 100644 src/main/resources/v2/kotlin-client/infrastructure/RequestConfig.kt.mustache create mode 100644 src/main/resources/v2/kotlin-client/infrastructure/RequestMethod.kt.mustache create mode 100644 src/main/resources/v2/kotlin-client/infrastructure/ResponseExtensions.kt.mustache create mode 100644 src/main/resources/v2/kotlin-client/infrastructure/Serializer.kt.mustache create mode 100644 src/main/resources/v2/kotlin-client/licenseInfo.mustache create mode 100644 src/main/resources/v2/kotlin-client/model.mustache create mode 100644 src/main/resources/v2/kotlin-client/model_doc.mustache create mode 100644 src/main/resources/v2/kotlin-client/settings.gradle.mustache create mode 100644 src/main/resources/v2/kotlin-server/README.mustache create mode 100644 src/main/resources/v2/kotlin-server/api_doc.mustache create mode 100644 src/main/resources/v2/kotlin-server/class_doc.mustache create mode 100644 src/main/resources/v2/kotlin-server/data_class.mustache create mode 100644 src/main/resources/v2/kotlin-server/data_class_opt_var.mustache create mode 100644 src/main/resources/v2/kotlin-server/data_class_req_var.mustache create mode 100644 src/main/resources/v2/kotlin-server/enum_class.mustache create mode 100644 src/main/resources/v2/kotlin-server/enum_doc.mustache create mode 100644 src/main/resources/v2/kotlin-server/libraries/ktor/ApiKeyAuth.kt.mustache create mode 100644 src/main/resources/v2/kotlin-server/libraries/ktor/AppMain.kt.mustache create mode 100644 src/main/resources/v2/kotlin-server/libraries/ktor/Configuration.kt.mustache create mode 100644 src/main/resources/v2/kotlin-server/libraries/ktor/Dockerfile.mustache create mode 100644 src/main/resources/v2/kotlin-server/libraries/ktor/Paths.kt.mustache create mode 100644 src/main/resources/v2/kotlin-server/libraries/ktor/README.mustache create mode 100644 src/main/resources/v2/kotlin-server/libraries/ktor/_api_body.mustache create mode 100644 src/main/resources/v2/kotlin-server/libraries/ktor/_principal.mustache create mode 100644 src/main/resources/v2/kotlin-server/libraries/ktor/_response.mustache create mode 100644 src/main/resources/v2/kotlin-server/libraries/ktor/api.mustache create mode 100644 src/main/resources/v2/kotlin-server/libraries/ktor/application.conf.mustache create mode 100644 src/main/resources/v2/kotlin-server/libraries/ktor/build.gradle.mustache create mode 100644 src/main/resources/v2/kotlin-server/libraries/ktor/gradle.properties create mode 100644 src/main/resources/v2/kotlin-server/libraries/ktor/licenseInfo.mustache create mode 100644 src/main/resources/v2/kotlin-server/libraries/ktor/logback.xml create mode 100644 src/main/resources/v2/kotlin-server/licenseInfo.mustache create mode 100644 src/main/resources/v2/kotlin-server/model.mustache create mode 100644 src/main/resources/v2/kotlin-server/model_doc.mustache create mode 100644 src/main/resources/v2/kotlin-server/settings.gradle.mustache create mode 100644 src/test/java/io/swagger/codegen/languages/kotlin/KotlinClientCodegenModelTest.java create mode 100644 src/test/java/io/swagger/codegen/languages/kotlin/KotlinClientCodegenOptionsTest.java create mode 100644 src/test/java/io/swagger/codegen/options/KotlinClientCodegenOptionsProvider.java diff --git a/src/main/java/io/swagger/codegen/languages/kotlin/AbstractKotlinCodegen.java b/src/main/java/io/swagger/codegen/languages/kotlin/AbstractKotlinCodegen.java new file mode 100644 index 0000000000..3f2f70b94e --- /dev/null +++ b/src/main/java/io/swagger/codegen/languages/kotlin/AbstractKotlinCodegen.java @@ -0,0 +1,553 @@ +package io.swagger.codegen.languages.kotlin; + +import io.swagger.codegen.CliOption; +import io.swagger.codegen.languages.DefaultCodegenConfig; +import io.swagger.codegen.CodegenConstants; +import io.swagger.codegen.DefaultCodegen; +import io.swagger.v3.oas.models.media.Schema; +import io.swagger.v3.oas.models.media.ArraySchema; +import io.swagger.v3.oas.models.media.MapSchema; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; + +public abstract class AbstractKotlinCodegen extends DefaultCodegenConfig { + static Logger LOGGER = LoggerFactory.getLogger(AbstractKotlinCodegen.class); + + protected String artifactId; + protected String artifactVersion = "1.0.0"; + protected String groupId = "io.swagger"; + protected String packageName; + + protected String sourceFolder = "src/main/kotlin"; + + protected String apiDocPath = "docs/"; + protected String modelDocPath = "docs/"; + + protected CodegenConstants.ENUM_PROPERTY_NAMING_TYPE enumPropertyNaming = CodegenConstants.ENUM_PROPERTY_NAMING_TYPE.camelCase; + + public AbstractKotlinCodegen() { + super(); + supportsInheritance = true; + + languageSpecificPrimitives = new HashSet(Arrays.asList( + "kotlin.Byte", + "kotlin.Short", + "kotlin.Int", + "kotlin.Long", + "kotlin.Float", + "kotlin.Double", + "kotlin.Boolean", + "kotlin.Char", + "kotlin.String", + "kotlin.Array", + "kotlin.collections.List", + "kotlin.collections.Map", + "kotlin.collections.Set" + )); + + // this includes hard reserved words defined by https://github.com/JetBrains/kotlin/blob/master/core/descriptors/src/org/jetbrains/kotlin/renderer/KeywordStringsGenerated.java + // as well as keywords from https://kotlinlang.org/docs/reference/keyword-reference.html + reservedWords = new HashSet(Arrays.asList( + "abstract", + "annotation", + "as", + "break", + "case", + "catch", + "class", + "companion", + "const", + "constructor", + "continue", + "crossinline", + "data", + "delegate", + "do", + "else", + "enum", + "external", + "false", + "final", + "finally", + "for", + "fun", + "if", + "in", + "infix", + "init", + "inline", + "inner", + "interface", + "internal", + "is", + "it", + "lateinit", + "lazy", + "noinline", + "null", + "object", + "open", + "operator", + "out", + "override", + "package", + "private", + "protected", + "public", + "reified", + "return", + "sealed", + "super", + "suspend", + "tailrec", + "this", + "throw", + "true", + "try", + "typealias", + "typeof", + "val", + "var", + "vararg", + "when", + "while" + )); + + defaultIncludes = new HashSet(Arrays.asList( + "kotlin.Byte", + "kotlin.Short", + "kotlin.Int", + "kotlin.Long", + "kotlin.Float", + "kotlin.Double", + "kotlin.Boolean", + "kotlin.Char", + "kotlin.Array", + "kotlin.collections.List", + "kotlin.collections.Set", + "kotlin.collections.Map" + )); + + typeMapping = new HashMap(); + typeMapping.put("string", "kotlin.String"); + typeMapping.put("boolean", "kotlin.Boolean"); + typeMapping.put("integer", "kotlin.Int"); + typeMapping.put("float", "kotlin.Float"); + typeMapping.put("long", "kotlin.Long"); + typeMapping.put("double", "kotlin.Double"); + typeMapping.put("number", "java.math.BigDecimal"); + typeMapping.put("date-time", "java.time.LocalDateTime"); + typeMapping.put("date", "java.time.LocalDateTime"); + typeMapping.put("file", "java.io.File"); + typeMapping.put("array", "kotlin.Array"); + typeMapping.put("list", "kotlin.Array"); + typeMapping.put("map", "kotlin.collections.Map"); + typeMapping.put("object", "kotlin.Any"); + typeMapping.put("binary", "kotlin.Array"); + typeMapping.put("Date", "java.time.LocalDateTime"); + typeMapping.put("DateTime", "java.time.LocalDateTime"); + + instantiationTypes.put("array", "arrayOf"); + instantiationTypes.put("list", "arrayOf"); + instantiationTypes.put("map", "mapOf"); + + importMapping = new HashMap(); + importMapping.put("BigDecimal", "java.math.BigDecimal"); + importMapping.put("UUID", "java.util.UUID"); + importMapping.put("File", "java.io.File"); + importMapping.put("Date", "java.util.Date"); + importMapping.put("Timestamp", "java.sql.Timestamp"); + importMapping.put("DateTime", "java.time.LocalDateTime"); + importMapping.put("LocalDateTime", "java.time.LocalDateTime"); + importMapping.put("LocalDate", "java.time.LocalDate"); + importMapping.put("LocalTime", "java.time.LocalTime"); + + specialCharReplacements.put(";", "Semicolon"); + + cliOptions.clear(); + addOption(CodegenConstants.SOURCE_FOLDER, CodegenConstants.SOURCE_FOLDER_DESC, sourceFolder); + addOption(CodegenConstants.PACKAGE_NAME, "Generated artifact package name (e.g. io.swagger).", packageName); + addOption(CodegenConstants.GROUP_ID, "Generated artifact package's organization (i.e. maven groupId).", groupId); + addOption(CodegenConstants.ARTIFACT_ID, "Generated artifact id (name of jar).", artifactId); + addOption(CodegenConstants.ARTIFACT_VERSION, "Generated artifact's package version.", artifactVersion); + + CliOption enumPropertyNamingOpt = new CliOption(CodegenConstants.ENUM_PROPERTY_NAMING, CodegenConstants.ENUM_PROPERTY_NAMING_DESC); + cliOptions.add(enumPropertyNamingOpt.defaultValue(enumPropertyNaming.name())); + } + + protected void addOption(String key, String description) { + addOption(key, description, null); + } + + protected void addOption(String key, String description, String defaultValue) { + CliOption option = new CliOption(key, description); + if (defaultValue != null) option.defaultValue(defaultValue); + cliOptions.add(option); + } + + protected void addSwitch(String key, String description, Boolean defaultValue) { + CliOption option = CliOption.newBoolean(key, description); + if (defaultValue != null) option.defaultValue(defaultValue.toString()); + cliOptions.add(option); + } + + @Override + public String apiDocFileFolder() { + return (outputFolder + "/" + apiDocPath).replace('/', File.separatorChar); + } + + @Override + public String apiFileFolder() { + return outputFolder + File.separator + sourceFolder + File.separator + apiPackage().replace('.', File.separatorChar); + } + + @Override + public String escapeQuotationMark(String input) { + // remove " to avoid code injection + return input.replace("\"", ""); + } + + @Override + public String escapeReservedWord(String name) { + // TODO: Allow enum escaping as an option (e.g. backticks vs append/prepend underscore vs match model property escaping). + return String.format("`%s`", name); + } + + @Override + public String escapeUnsafeCharacters(String input) { + return input.replace("*/", "*_/").replace("/*", "/_*"); + } + + public CodegenConstants.ENUM_PROPERTY_NAMING_TYPE getEnumPropertyNaming() { + return this.enumPropertyNaming; + } + + /** + * Sets the naming convention for Kotlin enum properties + * + * @param enumPropertyNamingType The string representation of the naming convention, as defined by {@link CodegenConstants.ENUM_PROPERTY_NAMING_TYPE} + */ + public void setEnumPropertyNaming(final String enumPropertyNamingType) { + try { + this.enumPropertyNaming = CodegenConstants.ENUM_PROPERTY_NAMING_TYPE.valueOf(enumPropertyNamingType); + } catch (IllegalArgumentException ex) { + StringBuilder sb = new StringBuilder(enumPropertyNamingType + " is an invalid enum property naming option. Please choose from:"); + for (CodegenConstants.ENUM_PROPERTY_NAMING_TYPE t : CodegenConstants.ENUM_PROPERTY_NAMING_TYPE.values()) { + sb.append("\n ").append(t.name()); + } + throw new RuntimeException(sb.toString()); + } + } + + // /** + // * returns the swagger type for the property + // * + // * @param p Swagger property object + // * @return string presentation of the type + // **/ + // @Override + // public String getSwaggerType(Schema p) { + // String swaggerType = super.getSwaggerType(p); + // String type; + // // This maps, for example, long -> kotlin.Long based on hashes in this type's constructor + // if (typeMapping.containsKey(swaggerType)) { + // type = typeMapping.get(swaggerType); + // if (languageSpecificPrimitives.contains(type)) { + // return toModelName(type); + // } + // } else { + // type = swaggerType; + // } + // return toModelName(type); + // } + + + /** + * Output the type declaration of the property + * + * @param propertySchema Swagger Property object + * @return a string presentation of the property type + */ + @Override + public String getTypeDeclaration(Schema propertySchema) { + if (propertySchema instanceof ArraySchema) { + return getArrayTypeDeclaration((ArraySchema) propertySchema); + } else if (propertySchema instanceof MapSchema) { + Schema inner = (Schema) propertySchema.getAdditionalProperties(); + if (inner == null) { + LOGGER.warn(propertySchema.getName() + "(map property) does not have a proper inner type defined"); + // TODO maybe better defaulting to StringProperty than returning null + return null; + } + return String.format("%s<%s>", getSchemaType(propertySchema), getTypeDeclaration(inner)); + // Maps will be keyed only by primitive Kotlin string + // return getSwaggerType(p) + ""; + } + return super.getTypeDeclaration(propertySchema); + } + + @Override + public String modelDocFileFolder() { + return (outputFolder + "/" + modelDocPath).replace('/', File.separatorChar); + } + + @Override + public String modelFileFolder() { + return outputFolder + File.separator + sourceFolder + File.separator + modelPackage().replace('.', File.separatorChar); + } + + @Override + public Map postProcessModels(Map objs) { + return postProcessModelsEnum(super.postProcessModels(objs)); + } + + @Override + public void processOpts() { + super.processOpts(); + + if (additionalProperties.containsKey(CodegenConstants.ENUM_PROPERTY_NAMING)) { + setEnumPropertyNaming((String) additionalProperties.get(CodegenConstants.ENUM_PROPERTY_NAMING)); + } + + if (additionalProperties.containsKey(CodegenConstants.SOURCE_FOLDER)) { + this.setSourceFolder((String) additionalProperties.get(CodegenConstants.SOURCE_FOLDER)); + } else { + additionalProperties.put(CodegenConstants.SOURCE_FOLDER, sourceFolder); + } + + if (additionalProperties.containsKey(CodegenConstants.PACKAGE_NAME)) { + this.setPackageName((String) additionalProperties.get(CodegenConstants.PACKAGE_NAME)); + if (!additionalProperties.containsKey(CodegenConstants.MODEL_PACKAGE)) + this.setModelPackage(packageName + ".models"); + if (!additionalProperties.containsKey(CodegenConstants.API_PACKAGE)) + this.setApiPackage(packageName + ".apis"); + } else { + additionalProperties.put(CodegenConstants.PACKAGE_NAME, packageName); + } + + if (additionalProperties.containsKey(CodegenConstants.ARTIFACT_ID)) { + this.setArtifactId((String) additionalProperties.get(CodegenConstants.ARTIFACT_ID)); + } else { + additionalProperties.put(CodegenConstants.ARTIFACT_ID, artifactId); + } + + if (additionalProperties.containsKey(CodegenConstants.GROUP_ID)) { + this.setGroupId((String) additionalProperties.get(CodegenConstants.GROUP_ID)); + } else { + additionalProperties.put(CodegenConstants.GROUP_ID, groupId); + } + + if (additionalProperties.containsKey(CodegenConstants.ARTIFACT_VERSION)) { + this.setArtifactVersion((String) additionalProperties.get(CodegenConstants.ARTIFACT_VERSION)); + } else { + additionalProperties.put(CodegenConstants.ARTIFACT_VERSION, artifactVersion); + } + + if (additionalProperties.containsKey(CodegenConstants.INVOKER_PACKAGE)) { + LOGGER.warn(CodegenConstants.INVOKER_PACKAGE + " with " + this.getName() + " generator is ignored. Use " + CodegenConstants.PACKAGE_NAME + "."); + } + + additionalProperties.put(CodegenConstants.API_PACKAGE, apiPackage()); + additionalProperties.put(CodegenConstants.MODEL_PACKAGE, modelPackage()); + + additionalProperties.put("apiDocPath", apiDocPath); + additionalProperties.put("modelDocPath", modelDocPath); + } + + public void setArtifactId(String artifactId) { + this.artifactId = artifactId; + } + + public void setArtifactVersion(String artifactVersion) { + this.artifactVersion = artifactVersion; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public void setPackageName(String packageName) { + this.packageName = packageName; + } + + public void setSourceFolder(String sourceFolder) { + this.sourceFolder = sourceFolder; + } + + /** + * Return the sanitized variable name for enum + * + * @param value enum variable name + * @param datatype data type + * @return the sanitized variable name for enum + */ + @Override + public String toEnumVarName(String value, String datatype) { + String modified; + if (value.length() == 0) { + modified = "EMPTY"; + } else { + modified = value; + modified = sanitizeKotlinSpecificNames(modified); + } + + switch (getEnumPropertyNaming()) { + case original: + // NOTE: This is provided as a last-case allowance, but will still result in reserved words being escaped. + modified = value; + break; + case camelCase: + // NOTE: Removes hyphens and underscores + modified = camelize(modified, true); + break; + case PascalCase: + // NOTE: Removes hyphens and underscores + String result = camelize(modified); + modified = titleCase(result); + break; + case snake_case: + // NOTE: Removes hyphens + modified = underscore(modified); + break; + case UPPERCASE: + modified = modified.toUpperCase(); + break; + } + + if (reservedWords.contains(modified)) { + return escapeReservedWord(modified); + } + + return modified; + } + + @Override + public String toInstantiationType(Schema p) { + if (p instanceof ArraySchema) { + return getArrayTypeDeclaration((ArraySchema) p); + } + return super.toInstantiationType(p); + } + + /** + * Return the fully-qualified "Model" name for import + * + * @param name the name of the "Model" + * @return the fully-qualified "Model" name for import + */ + @Override + public String toModelImport(String name) { + // toModelImport is called while processing operations, but DefaultCodegen doesn't + // define imports correctly with fully qualified primitives and models as defined in this generator. + if (needToImport(name)) { + return super.toModelImport(name); + } + + return name; + } + + /** + * Output the proper model name (capitalized). + * In case the name belongs to the TypeSystem it won't be renamed. + * + * @param name the name of the model + * @return capitalized model name + */ + @Override + public String toModelName(final String name) { + // Allow for explicitly configured kotlin.* and java.* types + if (name.startsWith("kotlin.") || name.startsWith("java.")) { + return name; + } + + // If importMapping contains name, assume this is a legitimate model name. + if (importMapping.containsKey(name)) { + return importMapping.get(name); + } + + String modifiedName = name.replaceAll("\\.", ""); + modifiedName = sanitizeKotlinSpecificNames(modifiedName); + + if (reservedWords.contains(modifiedName)) { + modifiedName = escapeReservedWord(modifiedName); + } + + return titleCase(modifiedName); + } + + /** + * Provides a strongly typed declaration for simple arrays of some type and arrays of arrays of some type. + * + * @param arr + * @return + */ + private String getArrayTypeDeclaration(ArraySchema arraySchema) { + // TODO: collection type here should be fully qualified namespace to avoid model conflicts + // This supports arrays of arrays. + String arrayType = typeMapping.get("array"); + StringBuilder instantiationType = new StringBuilder(arrayType); + Schema items = arraySchema.getItems(); + String nestedType = getTypeDeclaration(items); + // TODO: We may want to differentiate here between generics and primitive arrays. + instantiationType.append("<").append(nestedType).append(">"); + return instantiationType.toString(); + } + + /** + * Sanitize against Kotlin specific naming conventions, which may differ from those required by {@link DefaultCodegen#sanitizeName}. + * + * @param name string to be sanitize + * @return sanitized string + */ + private String sanitizeKotlinSpecificNames(final String name) { + String word = name; + for (Map.Entry specialCharacters : specialCharReplacements.entrySet()) { + // Underscore is the only special character we'll allow + if (!specialCharacters.getKey().equals("_")) { + word = word.replaceAll("\\Q" + specialCharacters.getKey() + "\\E", specialCharacters.getValue()); + } + } + + // Fallback, replace unknowns with underscore. + word = word.replaceAll("\\W+", "_"); + if (word.matches("\\d.*")) { + word = "_" + word; + } + + // _, __, and ___ are reserved in Kotlin. Treat all names with only underscores consistently, regardless of count. + if (word.matches("^_*$")) { + word = word.replaceAll("\\Q_\\E", "Underscore"); + } + + return word; + } + + private String titleCase(final String input) { + return input.substring(0, 1).toUpperCase() + input.substring(1); + } + + @Override + protected boolean isReservedWord(String word) { + // We want case-sensitive escaping, to avoid unnecessary backtick-escaping. + return reservedWords.contains(word); + } + + /** + * Check the type to see if it needs import the library/module/package + * + * @param type name of the type + * @return true if the library/module/package of the corresponding type needs to be imported + */ + @Override + protected boolean needToImport(String type) { + // provides extra protection against improperly trying to import language primitives and java types + boolean imports = !type.startsWith("kotlin.") && !type.startsWith("java.") && !defaultIncludes.contains(type) && !languageSpecificPrimitives.contains(type); + return imports; + } +} diff --git a/src/main/java/io/swagger/codegen/languages/kotlin/KotlinClientCodegen.java b/src/main/java/io/swagger/codegen/languages/kotlin/KotlinClientCodegen.java new file mode 100644 index 0000000000..ef2c20983a --- /dev/null +++ b/src/main/java/io/swagger/codegen/languages/kotlin/KotlinClientCodegen.java @@ -0,0 +1,118 @@ +package io.swagger.codegen.languages.kotlin; + +import io.swagger.codegen.CliOption; +import io.swagger.codegen.CodegenConstants; +import io.swagger.codegen.CodegenType; +import io.swagger.codegen.SupportingFile; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +public class KotlinClientCodegen extends AbstractKotlinCodegen { + + public static final String DATE_LIBRARY = "dateLibrary"; + protected CodegenConstants.ENUM_PROPERTY_NAMING_TYPE enumPropertyNaming = CodegenConstants.ENUM_PROPERTY_NAMING_TYPE.camelCase; + static Logger LOGGER = LoggerFactory.getLogger(KotlinClientCodegen.class); + + protected String dateLibrary = DateLibrary.JAVA8.value; + + public enum DateLibrary { + STRING("string"), + THREETENBP("threetenbp"), + JAVA8("java8"); + + public final String value; + + DateLibrary(String value) { + this.value = value; + } + } + + /** + * Constructs an instance of `KotlinClientCodegen`. + */ + public KotlinClientCodegen() { + super(); + + artifactId = "kotlin-client"; + packageName = "io.swagger.client"; + + outputFolder = "generated-code" + File.separator + "kotlin-client"; + modelTemplateFiles.put("model.mustache", ".kt"); + apiTemplateFiles.put("api.mustache", ".kt"); + modelDocTemplateFiles.put("model_doc.mustache", ".md"); + apiDocTemplateFiles.put("api_doc.mustache", ".md"); + embeddedTemplateDir = templateDir = "kotlin-client"; + apiPackage = packageName + ".apis"; + modelPackage = packageName + ".models"; + + CliOption dateLibrary = new CliOption(DATE_LIBRARY, "Option. Date library to use"); + Map dateOptions = new HashMap<>(); + dateOptions.put(DateLibrary.THREETENBP.value, "Threetenbp"); + dateOptions.put(DateLibrary.STRING.value, "String"); + dateOptions.put(DateLibrary.JAVA8.value, "Java 8 native JSR310"); + dateLibrary.setEnum(dateOptions); + cliOptions.add(dateLibrary); + } + + public CodegenType getTag() { + return CodegenType.CLIENT; + } + + public String getName() { + return "kotlin"; + } + + public String getHelp() { + return "Generates a kotlin client."; + } + + public void setDateLibrary(String library) { + this.dateLibrary = library; + } + + @Override + public void processOpts() { + super.processOpts(); + + if (additionalProperties.containsKey(DATE_LIBRARY)) { + setDateLibrary(additionalProperties.get(DATE_LIBRARY).toString()); + } + + if (DateLibrary.THREETENBP.value.equals(dateLibrary)) { + additionalProperties.put(DateLibrary.THREETENBP.value, true); + typeMapping.put("date", "LocalDate"); + typeMapping.put("DateTime", "LocalDateTime"); + importMapping.put("LocalDate", "org.threeten.bp.LocalDate"); + importMapping.put("LocalDateTime", "org.threeten.bp.LocalDateTime"); + defaultIncludes.add("org.threeten.bp.LocalDateTime"); + } else if (DateLibrary.STRING.value.equals(dateLibrary)) { + typeMapping.put("date-time", "kotlin.String"); + typeMapping.put("date", "kotlin.String"); + typeMapping.put("Date", "kotlin.String"); + typeMapping.put("DateTime", "kotlin.String"); + } else if (DateLibrary.JAVA8.value.equals(dateLibrary)) { + additionalProperties.put(DateLibrary.JAVA8.value, true); + } + + supportingFiles.add(new SupportingFile("README.mustache", "", "README.md")); + + supportingFiles.add(new SupportingFile("build.gradle.mustache", "", "build.gradle")); + supportingFiles.add(new SupportingFile("settings.gradle.mustache", "", "settings.gradle")); + + final String infrastructureFolder = (sourceFolder + File.separator + packageName + File.separator + "infrastructure").replace(".", "/"); + + supportingFiles.add(new SupportingFile("infrastructure/ApiClient.kt.mustache", infrastructureFolder, "ApiClient.kt")); + supportingFiles.add(new SupportingFile("infrastructure/ApiAbstractions.kt.mustache", infrastructureFolder, "ApiAbstractions.kt")); + supportingFiles.add(new SupportingFile("infrastructure/ApiInfrastructureResponse.kt.mustache", infrastructureFolder, "ApiInfrastructureResponse.kt")); + supportingFiles.add(new SupportingFile("infrastructure/ApplicationDelegates.kt.mustache", infrastructureFolder, "ApplicationDelegates.kt")); + supportingFiles.add(new SupportingFile("infrastructure/RequestConfig.kt.mustache", infrastructureFolder, "RequestConfig.kt")); + supportingFiles.add(new SupportingFile("infrastructure/RequestMethod.kt.mustache", infrastructureFolder, "RequestMethod.kt")); + supportingFiles.add(new SupportingFile("infrastructure/ResponseExtensions.kt.mustache", infrastructureFolder, "ResponseExtensions.kt")); + supportingFiles.add(new SupportingFile("infrastructure/Serializer.kt.mustache", infrastructureFolder, "Serializer.kt")); + supportingFiles.add(new SupportingFile("infrastructure/Errors.kt.mustache", infrastructureFolder, "Errors.kt")); + } +} diff --git a/src/main/java/io/swagger/codegen/languages/kotlin/KotlinServerCodegen.java b/src/main/java/io/swagger/codegen/languages/kotlin/KotlinServerCodegen.java new file mode 100644 index 0000000000..b0781d8369 --- /dev/null +++ b/src/main/java/io/swagger/codegen/languages/kotlin/KotlinServerCodegen.java @@ -0,0 +1,224 @@ +package io.swagger.codegen.languages.kotlin; + +import com.google.common.collect.ImmutableMap; +// import com.samskivert.mustache.Mustache; +import io.swagger.codegen.CliOption; +import io.swagger.codegen.CodegenConstants; +import io.swagger.codegen.CodegenType; +import io.swagger.codegen.SupportingFile; +// import io.swagger.codegen.mustache.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +public class KotlinServerCodegen extends AbstractKotlinCodegen { + + public static final String DEFAULT_LIBRARY = Constants.KTOR; + static Logger LOGGER = LoggerFactory.getLogger(KotlinServerCodegen.class); + private Boolean autoHeadFeatureEnabled = true; + private Boolean conditionalHeadersFeatureEnabled = false; + private Boolean hstsFeatureEnabled = true; + private Boolean corsFeatureEnabled = false; + private Boolean compressionFeatureEnabled = true; + + // This is here to potentially warn the user when an option is not supoprted by the target framework. + private Map> optionsSupportedPerFramework = new ImmutableMap.Builder>() + .put(Constants.KTOR, Arrays.asList( + Constants.AUTOMATIC_HEAD_REQUESTS, + Constants.CONDITIONAL_HEADERS, + Constants.HSTS, + Constants.CORS, + Constants.COMPRESSION + )) + .build(); + + /** + * Constructs an instance of `KotlinServerCodegen`. + */ + public KotlinServerCodegen() { + super(); + + artifactId = "kotlin-server"; + packageName = "io.swagger.server"; + outputFolder = "generated-code" + File.separator + "kotlin-server"; + modelTemplateFiles.put("model.mustache", ".kt"); + apiTemplateFiles.put("api.mustache", ".kt"); + embeddedTemplateDir = templateDir = "kotlin-server"; + apiPackage = packageName + ".apis"; + modelPackage = packageName + ".models"; + + supportedLibraries.put("ktor", "ktor framework"); + + // TODO: Configurable server engine. Defaults to netty in build.gradle. + CliOption library = new CliOption(CodegenConstants.LIBRARY, "library template (sub-template) to use"); + library.setDefault(DEFAULT_LIBRARY); + library.setEnum(supportedLibraries); + + cliOptions.add(library); + + addSwitch(Constants.AUTOMATIC_HEAD_REQUESTS, Constants.AUTOMATIC_HEAD_REQUESTS_DESC, getAutoHeadFeatureEnabled()); + addSwitch(Constants.CONDITIONAL_HEADERS, Constants.CONDITIONAL_HEADERS_DESC, getConditionalHeadersFeatureEnabled()); + addSwitch(Constants.HSTS, Constants.HSTS_DESC, getHstsFeatureEnabled()); + addSwitch(Constants.CORS, Constants.CORS_DESC, getCorsFeatureEnabled()); + addSwitch(Constants.COMPRESSION, Constants.COMPRESSION_DESC, getCompressionFeatureEnabled()); + } + + public Boolean getAutoHeadFeatureEnabled() { + return autoHeadFeatureEnabled; + } + + public void setAutoHeadFeatureEnabled(Boolean autoHeadFeatureEnabled) { + this.autoHeadFeatureEnabled = autoHeadFeatureEnabled; + } + + public Boolean getCompressionFeatureEnabled() { + return compressionFeatureEnabled; + } + + public void setCompressionFeatureEnabled(Boolean compressionFeatureEnabled) { + this.compressionFeatureEnabled = compressionFeatureEnabled; + } + + public Boolean getConditionalHeadersFeatureEnabled() { + return conditionalHeadersFeatureEnabled; + } + + public void setConditionalHeadersFeatureEnabled(Boolean conditionalHeadersFeatureEnabled) { + this.conditionalHeadersFeatureEnabled = conditionalHeadersFeatureEnabled; + } + + public Boolean getCorsFeatureEnabled() { + return corsFeatureEnabled; + } + + public void setCorsFeatureEnabled(Boolean corsFeatureEnabled) { + this.corsFeatureEnabled = corsFeatureEnabled; + } + + public String getHelp() { + return "Generates a kotlin server."; + } + + public Boolean getHstsFeatureEnabled() { + return hstsFeatureEnabled; + } + + public void setHstsFeatureEnabled(Boolean hstsFeatureEnabled) { + this.hstsFeatureEnabled = hstsFeatureEnabled; + } + + public String getName() { + return "kotlin-server"; + } + + public CodegenType getTag() { + return CodegenType.SERVER; + } + + @Override + public void processOpts() { + super.processOpts(); + + if (additionalProperties.containsKey(CodegenConstants.LIBRARY)) { + this.setLibrary((String) additionalProperties.get(CodegenConstants.LIBRARY)); + } + + if (additionalProperties.containsKey(Constants.AUTOMATIC_HEAD_REQUESTS)) { + setAutoHeadFeatureEnabled(convertPropertyToBooleanAndWriteBack(Constants.AUTOMATIC_HEAD_REQUESTS)); + } else { + additionalProperties.put(Constants.AUTOMATIC_HEAD_REQUESTS, getAutoHeadFeatureEnabled()); + } + + if (additionalProperties.containsKey(Constants.CONDITIONAL_HEADERS)) { + setConditionalHeadersFeatureEnabled(convertPropertyToBooleanAndWriteBack(Constants.CONDITIONAL_HEADERS)); + } else { + additionalProperties.put(Constants.CONDITIONAL_HEADERS, getConditionalHeadersFeatureEnabled()); + } + + if (additionalProperties.containsKey(Constants.HSTS)) { + setHstsFeatureEnabled(convertPropertyToBooleanAndWriteBack(Constants.HSTS)); + } else { + additionalProperties.put(Constants.HSTS, getHstsFeatureEnabled()); + } + + if (additionalProperties.containsKey(Constants.CORS)) { + setCorsFeatureEnabled(convertPropertyToBooleanAndWriteBack(Constants.CORS)); + } else { + additionalProperties.put(Constants.CORS, getCorsFeatureEnabled()); + } + + if (additionalProperties.containsKey(Constants.COMPRESSION)) { + setCompressionFeatureEnabled(convertPropertyToBooleanAndWriteBack(Constants.COMPRESSION)); + } else { + additionalProperties.put(Constants.COMPRESSION, getCompressionFeatureEnabled()); + } + + // Boolean generateApis = additionalProperties.containsKey(CodegenConstants.GENERATE_APIS) && (Boolean)additionalProperties.get(CodegenConstants.GENERATE_APIS); + String packageFolder = (sourceFolder + File.separator + packageName).replace(".", File.separator); + String resourcesFolder = "src/main/resources"; // not sure this can be user configurable. + + supportingFiles.add(new SupportingFile("README.mustache", "", "README.md")); + supportingFiles.add(new SupportingFile("Dockerfile.mustache", "", "Dockerfile")); + + supportingFiles.add(new SupportingFile("build.gradle.mustache", "", "build.gradle")); + supportingFiles.add(new SupportingFile("settings.gradle.mustache", "", "settings.gradle")); + supportingFiles.add(new SupportingFile("gradle.properties", "", "gradle.properties")); + + supportingFiles.add(new SupportingFile("AppMain.kt.mustache", packageFolder, "AppMain.kt")); + supportingFiles.add(new SupportingFile("Configuration.kt.mustache", packageFolder, "Configuration.kt")); + + // if (generateApis) { + supportingFiles.add(new SupportingFile("Paths.kt.mustache", packageFolder, "Paths.kt")); + // } + + supportingFiles.add(new SupportingFile("application.conf.mustache", resourcesFolder, "application.conf")); + supportingFiles.add(new SupportingFile("logback.xml", resourcesFolder, "logback.xml")); + + final String infrastructureFolder = (sourceFolder + File.separator + packageName + File.separator + "infrastructure").replace(".", File.separator); + + supportingFiles.add(new SupportingFile("ApiKeyAuth.kt.mustache", infrastructureFolder, "ApiKeyAuth.kt")); + + addMustacheLambdas(additionalProperties); + } + + private void addMustacheLambdas(Map objs) { + + // Map lambdas = new ImmutableMap.Builder() + // .put("lowercase", new LowercaseLambda().generator(this)) + // .put("uppercase", new UppercaseLambda()) + // .put("titlecase", new TitlecaseLambda()) + // .put("camelcase", new CamelCaseLambda().generator(this)) + // .put("indented", new IndentedLambda()) + // .put("indented_8", new IndentedLambda(8, " ")) + // .put("indented_12", new IndentedLambda(12, " ")) + // .put("indented_16", new IndentedLambda(16, " ")) + // .build(); + + // if (objs.containsKey("lambda")) { + // LOGGER.warn("An property named 'lambda' already exists. Mustache lambdas renamed from 'lambda' to '_lambda'. " + + // "You'll likely need to use a custom template, " + + // "see https://github.com/swagger-api/swagger-codegen#modifying-the-client-library-format. "); + // objs.put("_lambda", lambdas); + // } else { + // objs.put("lambda", lambdas); + // } + } + + public static class Constants { + public final static String KTOR = "ktor"; + public final static String AUTOMATIC_HEAD_REQUESTS = "featureAutoHead"; + public final static String AUTOMATIC_HEAD_REQUESTS_DESC = "Automatically provide responses to HEAD requests for existing routes that have the GET verb defined."; + public final static String CONDITIONAL_HEADERS = "featureConditionalHeaders"; + public final static String CONDITIONAL_HEADERS_DESC = "Avoid sending content if client already has same content, by checking ETag or LastModified properties."; + public final static String HSTS = "featureHSTS"; + public final static String HSTS_DESC = "Avoid sending content if client already has same content, by checking ETag or LastModified properties."; + public final static String CORS = "featureCORS"; + public final static String CORS_DESC = "Ktor by default provides an interceptor for implementing proper support for Cross-Origin Resource Sharing (CORS). See enable-cors.org."; + public final static String COMPRESSION = "featureCompression"; + public final static String COMPRESSION_DESC = "Adds ability to compress outgoing content using gzip, deflate or custom encoder and thus reduce size of the response."; + } +} diff --git a/src/main/resources/v2/kotlin-client/README.mustache b/src/main/resources/v2/kotlin-client/README.mustache new file mode 100644 index 0000000000..7a10bab6c1 --- /dev/null +++ b/src/main/resources/v2/kotlin-client/README.mustache @@ -0,0 +1,85 @@ +# {{packageName}} - Kotlin client library for {{appName}} + +## Requires + +* Kotlin 1.1.2 +* Gradle 3.3 + +## Build + +First, create the gradle wrapper script: + +``` +gradle wrapper +``` + +Then, run: + +``` +./gradlew check assemble +``` + +This runs all tests and packages the library. + +## Features/Implementation Notes + +* Supports JSON inputs/outputs, File inputs, and Form inputs. +* Supports collection formats for query parameters: csv, tsv, ssv, pipes. +* Some Kotlin and Java types are fully qualified to avoid conflicts with types defined in Swagger definitions. +* Implementation of ApiClient is intended to reduce method counts, specifically to benefit Android targets. + +{{#generateApiDocs}} + +## Documentation for API Endpoints + +All URIs are relative to *{{{basePath}}}* + +Class | Method | HTTP request | Description +------------ | ------------- | ------------- | ------------- +{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}*{{classname}}* | [**{{operationId}}**]({{apiDocPath}}{{classname}}.md#{{operationIdLowerCase}}) | **{{httpMethod}}** {{path}} | {{#summary}}{{{summary}}}{{/summary}} +{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}} +{{/generateApiDocs}} + +{{#generateModelDocs}} + +## Documentation for Models + +{{#modelPackage}} +{{#models}}{{#model}} - [{{{modelPackage}}}.{{{classname}}}]({{modelDocPath}}{{{classname}}}.md) +{{/model}}{{/models}} +{{/modelPackage}} +{{^modelPackage}} +No model defined in this package +{{/modelPackage}} +{{/generateModelDocs}} + +{{! TODO: optional documentation for authorization? }} +## Documentation for Authorization + +{{^authMethods}} +All endpoints do not require authorization. +{{/authMethods}} +{{#authMethods}} +{{#last}} +Authentication schemes defined for the API: +{{/last}} +{{/authMethods}} +{{#authMethods}} + +### {{name}} + +{{#isApiKey}}- **Type**: API key +- **API key parameter name**: {{keyParamName}} +- **Location**: {{#isKeyInQuery}}URL query string{{/isKeyInQuery}}{{#isKeyInHeader}}HTTP header{{/isKeyInHeader}} +{{/isApiKey}} +{{#isBasic}}- **Type**: HTTP basic authentication +{{/isBasic}} +{{#isOAuth}}- **Type**: OAuth +- **Flow**: {{flow}} +- **Authorization URL**: {{authorizationUrl}} +- **Scopes**: {{^scopes}}N/A{{/scopes}} +{{#scopes}} - {{scope}}: {{description}} +{{/scopes}} +{{/isOAuth}} + +{{/authMethods}} \ No newline at end of file diff --git a/src/main/resources/v2/kotlin-client/api.mustache b/src/main/resources/v2/kotlin-client/api.mustache new file mode 100644 index 0000000000..f479302b7d --- /dev/null +++ b/src/main/resources/v2/kotlin-client/api.mustache @@ -0,0 +1,50 @@ +{{>licenseInfo}} +package {{apiPackage}} + +{{#imports}}import {{import}} +{{/imports}} + +import {{packageName}}.infrastructure.* +{{#threetenbp}} +import org.threeten.bp.LocalDateTime +{{/threetenbp}} + +{{#operations}} +class {{classname}}(basePath: kotlin.String = "{{{basePath}}}") : ApiClient(basePath) { + + {{#operation}} + /** + * {{summary}} + * {{notes}} + {{#allParams}}* @param {{paramName}} {{description}} {{^required}}(optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}} + {{/allParams}}* @return {{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}} + */{{#returnType}} + @Suppress("UNCHECKED_CAST"){{/returnType}} + fun {{operationId}}({{#allParams}}{{paramName}}: {{{dataType}}}{{#hasMore}}, {{/hasMore}}{{/allParams}}) : {{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}Unit{{/returnType}} { + val localVariableBody: kotlin.Any? = {{#hasBodyParam}}{{#bodyParams}}{{paramName}}{{/bodyParams}}{{/hasBodyParam}}{{^hasBodyParam}}{{^hasFormParams}}null{{/hasFormParams}}{{#hasFormParams}}mapOf({{#formParams}}"{{{baseName}}}" to "${{paramName}}"{{#hasMore}}, {{/hasMore}}{{/formParams}}){{/hasFormParams}}{{/hasBodyParam}} + 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}} + val localVariableHeaders: kotlin.collections.Map = mapOf({{#hasFormParams}}"Content-Type" to "multipart/form-data"{{/hasFormParams}}{{^hasHeaderParams}}){{/hasHeaderParams}}{{#hasHeaderParams}}{{#hasFormParams}}, {{/hasFormParams}}{{#headerParams}}"{{baseName}}" to {{#isContainer}}{{paramName}}.joinToString(separator = collectionDelimiter("{{collectionFormat}}"){{/isContainer}}{{^isContainer}}{{paramName}}{{/isContainer}}{{#hasMore}}, {{/hasMore}}{{/headerParams}}){{/hasHeaderParams}} + val localVariableConfig = RequestConfig( + RequestMethod.{{httpMethod}}, + "{{path}}"{{#pathParams}}.replace("{"+"{{baseName}}"+"}", "${{paramName}}"){{/pathParams}}, + query = localVariableQuery, + headers = localVariableHeaders + ) + val response = request<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}Any?{{/returnType}}>( + localVariableConfig, + localVariableBody + ) + + return when (response.responseType) { + ResponseType.Success -> {{#returnType}}(response as Success<*>).data as {{{returnType}}}{{/returnType}}{{^returnType}}Unit{{/returnType}} + ResponseType.Informational -> TODO() + ResponseType.Redirection -> TODO() + ResponseType.ClientError -> throw ClientException((response as ClientError<*>).body as? String ?: "Client error") + ResponseType.ServerError -> throw ServerException((response as ServerError<*>).message ?: "Server error") + else -> throw kotlin.IllegalStateException("Undefined ResponseType.") + } + } + + {{/operation}} +} +{{/operations}} diff --git a/src/main/resources/v2/kotlin-client/api_doc.mustache b/src/main/resources/v2/kotlin-client/api_doc.mustache new file mode 100644 index 0000000000..d288470ca8 --- /dev/null +++ b/src/main/resources/v2/kotlin-client/api_doc.mustache @@ -0,0 +1,65 @@ +# {{classname}}{{#description}} +{{description}}{{/description}} + +All URIs are relative to *{{basePath}}* + +Method | HTTP request | Description +------------- | ------------- | ------------- +{{#operations}}{{#operation}}[**{{operationId}}**]({{classname}}.md#{{operationId}}) | **{{httpMethod}}** {{path}} | {{#summary}}{{summary}}{{/summary}} +{{/operation}}{{/operations}} + +{{#operations}} +{{#operation}} + +# **{{operationId}}** +> {{#returnType}}{{returnType}} {{/returnType}}{{operationId}}({{#allParams}}{{{paramName}}}{{#hasMore}}, {{/hasMore}}{{/allParams}}) + +{{summary}}{{#notes}} + +{{notes}}{{/notes}} + +### Example +```kotlin +// Import classes: +//import {{{packageName}}}.infrastructure.* +//import {{{modelPackage}}}.* + +{{! TODO: Auth method documentation examples}} +val apiInstance = {{{classname}}}() +{{#allParams}} +val {{{paramName}}} : {{{dataType}}} = {{{example}}} // {{{dataType}}} | {{{description}}} +{{/allParams}} +try { + {{#returnType}}val result : {{{returnType}}} = {{/returnType}}apiInstance.{{{operationId}}}({{#allParams}}{{{paramName}}}{{#hasMore}}, {{/hasMore}}{{/allParams}}){{#returnType}} + println(result){{/returnType}} +} catch (e: ClientException) { + println("4xx response calling {{{classname}}}#{{{operationId}}}") + e.printStackTrace() +} catch (e: ServerException) { + println("5xx response calling {{{classname}}}#{{{operationId}}}") + e.printStackTrace() +} +``` + +### Parameters +{{^allParams}}This endpoint does not need any parameter.{{/allParams}}{{#allParams}}{{#-last}} +Name | Type | Description | Notes +------------- | ------------- | ------------- | -------------{{/-last}}{{/allParams}} +{{#allParams}} **{{paramName}}** | {{#isPrimitiveType}}**{{dataType}}**{{/isPrimitiveType}}{{^isPrimitiveType}}{{#isFile}}**{{dataType}}**{{/isFile}}{{^isFile}}{{#generateModelDocs}}[**{{dataType}}**]({{baseType}}.md){{/generateModelDocs}}{{^generateModelDocs}}**{{dataType}}**{{/generateModelDocs}}{{/isFile}}{{/isPrimitiveType}}| {{description}} |{{^required}} [optional]{{/required}}{{#defaultValue}} [default to {{defaultValue}}]{{/defaultValue}}{{#allowableValues}} [enum: {{#values}}{{{.}}}{{^-last}}, {{/-last}}{{/values}}]{{/allowableValues}} +{{/allParams}} + +### Return type + +{{#returnType}}{{#returnTypeIsPrimitive}}**{{returnType}}**{{/returnTypeIsPrimitive}}{{^returnTypeIsPrimitive}}{{#generateModelDocs}}[**{{returnType}}**]({{returnBaseType}}.md){{/generateModelDocs}}{{^generateModelDocs}}**{{returnType}}**{{/generateModelDocs}}{{/returnTypeIsPrimitive}}{{/returnType}}{{^returnType}}null (empty response body){{/returnType}} + +### Authorization + +{{^authMethods}}No authorization required{{/authMethods}}{{#authMethods}}[{{name}}](../README.md#{{name}}){{^-last}}, {{/-last}}{{/authMethods}} + +### HTTP request headers + + - **Content-Type**: {{#consumes}}{{{mediaType}}}{{#hasMore}}, {{/hasMore}}{{/consumes}}{{^consumes}}Not defined{{/consumes}} + - **Accept**: {{#produces}}{{{mediaType}}}{{#hasMore}}, {{/hasMore}}{{/produces}}{{^produces}}Not defined{{/produces}} + +{{/operation}} +{{/operations}} diff --git a/src/main/resources/v2/kotlin-client/build.gradle.mustache b/src/main/resources/v2/kotlin-client/build.gradle.mustache new file mode 100644 index 0000000000..7d02942c6d --- /dev/null +++ b/src/main/resources/v2/kotlin-client/build.gradle.mustache @@ -0,0 +1,33 @@ +group '{{groupId}}' +version '{{artifactVersion}}' + +task wrapper(type: Wrapper) { + gradleVersion = '3.3' + distributionUrl = "https://services.gradle.org/distributions/gradle-$gradleVersion-all.zip" +} + +buildscript { + ext.kotlin_version = '1.1.2' + + repositories { + mavenCentral() + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +apply plugin: 'kotlin' + +repositories { + mavenCentral() +} + +dependencies { + compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" + compile "com.squareup.moshi:moshi-kotlin:1.5.0" + compile "com.squareup.moshi:moshi-adapters:1.5.0" + compile "com.squareup.okhttp3:okhttp:3.8.0" + compile "org.threeten:threetenbp:1.3.6" + testCompile "io.kotlintest:kotlintest:2.0.2" +} diff --git a/src/main/resources/v2/kotlin-client/class_doc.mustache b/src/main/resources/v2/kotlin-client/class_doc.mustache new file mode 100644 index 0000000000..45e1994273 --- /dev/null +++ b/src/main/resources/v2/kotlin-client/class_doc.mustache @@ -0,0 +1,15 @@ +# {{classname}} + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +{{#vars}}**{{name}}** | {{#isEnum}}[**inline**](#{{datatypeWithEnum}}){{/isEnum}}{{^isEnum}}{{#isPrimitiveType}}**{{datatype}}**{{/isPrimitiveType}}{{^isPrimitiveType}}[**{{datatype}}**]({{complexType}}.md){{/isPrimitiveType}}{{/isEnum}} | {{description}} | {{^required}} [optional]{{/required}}{{#readOnly}} [readonly]{{/readOnly}} +{{/vars}} +{{#vars}}{{#isEnum}} + +{{!NOTE: see java's resources "pojo_doc.mustache" once enums are fully implemented}} +## Enum: {{baseName}} +Name | Value +---- | -----{{#allowableValues}} +{{name}} | {{#values}}{{.}}{{^-last}}, {{/-last}}{{/values}}{{/allowableValues}} +{{/isEnum}}{{/vars}} diff --git a/src/main/resources/v2/kotlin-client/data_class.mustache b/src/main/resources/v2/kotlin-client/data_class.mustache new file mode 100644 index 0000000000..e64785e01b --- /dev/null +++ b/src/main/resources/v2/kotlin-client/data_class.mustache @@ -0,0 +1,25 @@ +/** + * {{{description}}} +{{#vars}} + * @param {{name}} {{{description}}} +{{/vars}} + */ +data class {{classname}} ( +{{#requiredVars}} +{{>data_class_req_var}}{{^-last}}, +{{/-last}}{{/requiredVars}}{{#hasRequired}}{{#hasOptional}}, +{{/hasOptional}}{{/hasRequired}}{{#optionalVars}}{{>data_class_opt_var}}{{^-last}}, +{{/-last}}{{/optionalVars}} +) { +{{#hasEnums}}{{#vars}}{{#isEnum}} + /** + * {{{description}}} + * Values: {{#allowableValues}}{{#enumVars}}{{&name}}{{^-last}},{{/-last}}{{/enumVars}}{{/allowableValues}} + */ + enum class {{nameInCamelCase}}(val value: {{datatype}}){ + {{#allowableValues}}{{#enumVars}} + {{&name}}({{{value}}}){{^-last}},{{/-last}}{{#-last}};{{/-last}} + {{/enumVars}}{{/allowableValues}} + } +{{/isEnum}}{{/vars}}{{/hasEnums}} +} diff --git a/src/main/resources/v2/kotlin-client/data_class_opt_var.mustache b/src/main/resources/v2/kotlin-client/data_class_opt_var.mustache new file mode 100644 index 0000000000..a88761ea90 --- /dev/null +++ b/src/main/resources/v2/kotlin-client/data_class_opt_var.mustache @@ -0,0 +1,4 @@ +{{#description}} + /* {{{description}}} */ +{{/description}} + val {{{name}}}: {{#isEnum}}{{classname}}.{{nameInCamelCase}}{{/isEnum}}{{^isEnum}}{{{datatype}}}{{/isEnum}}? = {{#defaultvalue}}{{defaultvalue}}{{/defaultvalue}}{{^defaultvalue}}null{{/defaultvalue}} \ No newline at end of file diff --git a/src/main/resources/v2/kotlin-client/data_class_req_var.mustache b/src/main/resources/v2/kotlin-client/data_class_req_var.mustache new file mode 100644 index 0000000000..8a33a15a18 --- /dev/null +++ b/src/main/resources/v2/kotlin-client/data_class_req_var.mustache @@ -0,0 +1,4 @@ +{{#description}} + /* {{{description}}} */ +{{/description}} + val {{{name}}}: {{#isEnum}}{{classname}}.{{nameInCamelCase}}{{/isEnum}}{{^isEnum}}{{{datatype}}}{{/isEnum}} \ No newline at end of file diff --git a/src/main/resources/v2/kotlin-client/enum_class.mustache b/src/main/resources/v2/kotlin-client/enum_class.mustache new file mode 100644 index 0000000000..791398b978 --- /dev/null +++ b/src/main/resources/v2/kotlin-client/enum_class.mustache @@ -0,0 +1,9 @@ +/** +* {{{description}}} +* Values: {{#allowableValues}}{{#enumVars}}{{&name}}{{^-last}},{{/-last}}{{/enumVars}}{{/allowableValues}} +*/ +enum class {{classname}}(val value: {{dataType}}){ +{{#allowableValues}}{{#enumVars}} + {{&name}}({{{value}}}){{^-last}},{{/-last}}{{#-last}};{{/-last}} +{{/enumVars}}{{/allowableValues}} +} diff --git a/src/main/resources/v2/kotlin-client/enum_doc.mustache b/src/main/resources/v2/kotlin-client/enum_doc.mustache new file mode 100644 index 0000000000..fcb3d7e61a --- /dev/null +++ b/src/main/resources/v2/kotlin-client/enum_doc.mustache @@ -0,0 +1,7 @@ +# {{classname}} + +## Enum + +{{#allowableValues}}{{#enumVars}} + * `{{name}}` (value: `{{{value}}}`) +{{/enumVars}}{{/allowableValues}} diff --git a/src/main/resources/v2/kotlin-client/infrastructure/ApiAbstractions.kt.mustache b/src/main/resources/v2/kotlin-client/infrastructure/ApiAbstractions.kt.mustache new file mode 100644 index 0000000000..0a42ce534d --- /dev/null +++ b/src/main/resources/v2/kotlin-client/infrastructure/ApiAbstractions.kt.mustache @@ -0,0 +1,20 @@ +package {{packageName}}.infrastructure + +typealias MultiValueMap = Map> + +fun collectionDelimiter(collectionFormat: String) = when(collectionFormat) { + "csv" -> "," + "tsv" -> "\t" + "pipes" -> "|" + "ssv" -> " " + else -> "" +} + +val defaultMultiValueConverter: (item: Any?) -> String = { item -> "$item" } + +fun toMultiValue(items: List, collectionFormat: String, map: (item: Any?) -> String = defaultMultiValueConverter): List { + return when(collectionFormat) { + "multi" -> items.map(map) + else -> listOf(items.map(map).joinToString(separator = collectionDelimiter(collectionFormat))) + } +} \ No newline at end of file diff --git a/src/main/resources/v2/kotlin-client/infrastructure/ApiClient.kt.mustache b/src/main/resources/v2/kotlin-client/infrastructure/ApiClient.kt.mustache new file mode 100644 index 0000000000..e46256a356 --- /dev/null +++ b/src/main/resources/v2/kotlin-client/infrastructure/ApiClient.kt.mustache @@ -0,0 +1,128 @@ +package {{packageName}}.infrastructure + +import okhttp3.* +import java.io.File + +open class ApiClient(val baseUrl: String) { + companion object { + protected val ContentType = "Content-Type" + protected val Accept = "Accept" + protected val JsonMediaType = "application/json" + protected val FormDataMediaType = "multipart/form-data" + protected val XmlMediaType = "application/xml" + + @JvmStatic + val client : OkHttpClient = OkHttpClient() + + @JvmStatic + var defaultHeaders: Map by ApplicationDelegates.setOnce(mapOf(ContentType to JsonMediaType, Accept to JsonMediaType)) + + @JvmStatic + val jsonHeaders: Map = mapOf(ContentType to JsonMediaType, Accept to JsonMediaType) + } + + inline protected fun requestBody(content: T, mediaType: String = JsonMediaType): RequestBody { + if(content is File) { + return RequestBody.create( + MediaType.parse(mediaType), content + ) + } else if(mediaType == FormDataMediaType) { + var builder = FormBody.Builder() + // content's type *must* be Map + @Suppress("UNCHECKED_CAST") + (content as Map).forEach { key, value -> + builder = builder.add(key, value) + } + return builder.build() + } else if(mediaType == JsonMediaType) { + return RequestBody.create( + MediaType.parse(mediaType), Serializer.moshi.adapter(T::class.java).toJson(content) + ) + } else if (mediaType == XmlMediaType) { + TODO("xml not currently supported.") + } + + // TODO: this should be extended with other serializers + TODO("requestBody currently only supports JSON body and File body.") + } + + inline protected fun responseBody(body: ResponseBody?, mediaType: String = JsonMediaType): T? { + if(body == null) return null + return when(mediaType) { + JsonMediaType -> Serializer.moshi.adapter(T::class.java).fromJson(body.source()) + else -> TODO() + } + } + + inline protected fun request(requestConfig: RequestConfig, body : Any? = null): ApiInfrastructureResponse { + val httpUrl = HttpUrl.parse(baseUrl) ?: throw IllegalStateException("baseUrl is invalid.") + + var urlBuilder = httpUrl.newBuilder() + .addPathSegments(requestConfig.path.trimStart('/')) + + requestConfig.query.forEach { query -> + query.value.forEach { queryValue -> + urlBuilder = urlBuilder.addQueryParameter(query.key, queryValue) + } + } + + val url = urlBuilder.build() + val headers = requestConfig.headers + defaultHeaders + + if(headers[ContentType] ?: "" == "") { + throw kotlin.IllegalStateException("Missing Content-Type header. This is required.") + } + + if(headers[Accept] ?: "" == "") { + throw kotlin.IllegalStateException("Missing Accept header. This is required.") + } + + // TODO: support multiple contentType,accept options here. + val contentType = (headers[ContentType] as String).substringBefore(";").toLowerCase() + val accept = (headers[Accept] as String).substringBefore(";").toLowerCase() + + var request : Request.Builder = when (requestConfig.method) { + RequestMethod.DELETE -> Request.Builder().url(url).delete() + RequestMethod.GET -> Request.Builder().url(url) + RequestMethod.HEAD -> Request.Builder().url(url).head() + RequestMethod.PATCH -> Request.Builder().url(url).patch(requestBody(body, contentType)) + RequestMethod.PUT -> Request.Builder().url(url).put(requestBody(body, contentType)) + RequestMethod.POST -> Request.Builder().url(url).post(requestBody(body, contentType)) + RequestMethod.OPTIONS -> Request.Builder().url(url).method("OPTIONS", null) + } + + headers.forEach { header -> request = request.addHeader(header.key, header.value) } + + val realRequest = request.build() + val response = client.newCall(realRequest).execute() + + // TODO: handle specific mapping types. e.g. Map> + when { + response.isRedirect -> return Redirection( + response.code(), + response.headers().toMultimap() + ) + response.isInformational -> return Informational( + response.message(), + response.code(), + response.headers().toMultimap() + ) + response.isSuccessful -> return Success( + responseBody(response.body(), accept), + response.code(), + response.headers().toMultimap() + ) + response.isClientError -> return ClientError( + response.body()?.string(), + response.code(), + response.headers().toMultimap() + ) + else -> return ServerError( + null, + response.body()?.string(), + response.code(), + response.headers().toMultimap() + ) + } + } +} \ No newline at end of file diff --git a/src/main/resources/v2/kotlin-client/infrastructure/ApiInfrastructureResponse.kt.mustache b/src/main/resources/v2/kotlin-client/infrastructure/ApiInfrastructureResponse.kt.mustache new file mode 100644 index 0000000000..a4e0f0f424 --- /dev/null +++ b/src/main/resources/v2/kotlin-client/infrastructure/ApiInfrastructureResponse.kt.mustache @@ -0,0 +1,40 @@ +package {{packageName}}.infrastructure + +enum class ResponseType { + Success, Informational, Redirection, ClientError, ServerError +} + +abstract class ApiInfrastructureResponse(val responseType: ResponseType) { + abstract val statusCode: Int + abstract val headers: Map> +} + +class Success( + val data: T, + override val statusCode: Int = -1, + override val headers: Map> = mapOf() +): ApiInfrastructureResponse(ResponseType.Success) + +class Informational( + val statusText: String, + override val statusCode: Int = -1, + override val headers: Map> = mapOf() +) : ApiInfrastructureResponse(ResponseType.Informational) + +class Redirection( + override val statusCode: Int = -1, + override val headers: Map> = mapOf() +) : ApiInfrastructureResponse(ResponseType.Redirection) + +class ClientError( + val body: Any? = null, + override val statusCode: Int = -1, + override val headers: Map> = mapOf() +) : ApiInfrastructureResponse(ResponseType.ClientError) + +class ServerError( + val message: String? = null, + val body: Any? = null, + override val statusCode: Int = -1, + override val headers: Map> +): ApiInfrastructureResponse(ResponseType.ServerError) \ No newline at end of file diff --git a/src/main/resources/v2/kotlin-client/infrastructure/ApplicationDelegates.kt.mustache b/src/main/resources/v2/kotlin-client/infrastructure/ApplicationDelegates.kt.mustache new file mode 100644 index 0000000000..bc4e1cd6a1 --- /dev/null +++ b/src/main/resources/v2/kotlin-client/infrastructure/ApplicationDelegates.kt.mustache @@ -0,0 +1,29 @@ +package {{packageName}}.infrastructure + +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty + +object ApplicationDelegates { + /** + * Provides a property delegate, allowing the property to be set once and only once. + * + * If unset (no default value), a get on the property will throw [IllegalStateException]. + */ + fun setOnce(defaultValue: T? = null) : ReadWriteProperty = SetOnce(defaultValue) + + private class SetOnce(defaultValue: T? = null) : ReadWriteProperty { + private var isSet = false + private var value: T? = defaultValue + + override fun getValue(thisRef: Any?, property: KProperty<*>): T { + return value ?: throw IllegalStateException("${property.name} not initialized") + } + + override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) = synchronized(this) { + if (!isSet) { + this.value = value + isSet = true + } + } + } +} \ No newline at end of file diff --git a/src/main/resources/v2/kotlin-client/infrastructure/Errors.kt.mustache b/src/main/resources/v2/kotlin-client/infrastructure/Errors.kt.mustache new file mode 100644 index 0000000000..1f6d106d94 --- /dev/null +++ b/src/main/resources/v2/kotlin-client/infrastructure/Errors.kt.mustache @@ -0,0 +1,42 @@ +@file:Suppress("unused") +package {{packageName}}.infrastructure + +import java.lang.RuntimeException + +open class ClientException : RuntimeException { + + /** + * Constructs an [ClientException] with no detail message. + */ + constructor() : super() + + /** + * Constructs an [ClientException] with the specified detail message. + + * @param message the detail message. + */ + constructor(message: kotlin.String) : super(message) + + companion object { + private const val serialVersionUID: Long = 123L + } +} + +open class ServerException : RuntimeException { + + /** + * Constructs an [ServerException] with no detail message. + */ + constructor() : super() + + /** + * Constructs an [ServerException] with the specified detail message. + + * @param message the detail message. + */ + constructor(message: kotlin.String) : super(message) + + companion object { + private const val serialVersionUID: Long = 456L + } +} \ No newline at end of file diff --git a/src/main/resources/v2/kotlin-client/infrastructure/RequestConfig.kt.mustache b/src/main/resources/v2/kotlin-client/infrastructure/RequestConfig.kt.mustache new file mode 100644 index 0000000000..58a3d605aa --- /dev/null +++ b/src/main/resources/v2/kotlin-client/infrastructure/RequestConfig.kt.mustache @@ -0,0 +1,15 @@ +package {{packageName}}.infrastructure + +/** + * Defines a config object for a given request. + * NOTE: This object doesn't include 'body' because it + * allows for caching of the constructed object + * for many request definitions. + * NOTE: Headers is a Map because rfc2616 defines + * multi-valued headers as csv-only. + */ +data class RequestConfig( + val method: RequestMethod, + val path: String, + val headers: Map = mapOf(), + val query: Map> = mapOf()) \ No newline at end of file diff --git a/src/main/resources/v2/kotlin-client/infrastructure/RequestMethod.kt.mustache b/src/main/resources/v2/kotlin-client/infrastructure/RequestMethod.kt.mustache new file mode 100644 index 0000000000..5774235985 --- /dev/null +++ b/src/main/resources/v2/kotlin-client/infrastructure/RequestMethod.kt.mustache @@ -0,0 +1,8 @@ +package {{packageName}}.infrastructure + +/** + * Provides enumerated HTTP verbs + */ +enum class RequestMethod { + GET, DELETE, HEAD, OPTIONS, PATCH, POST, PUT +} \ No newline at end of file diff --git a/src/main/resources/v2/kotlin-client/infrastructure/ResponseExtensions.kt.mustache b/src/main/resources/v2/kotlin-client/infrastructure/ResponseExtensions.kt.mustache new file mode 100644 index 0000000000..2e2a3478ff --- /dev/null +++ b/src/main/resources/v2/kotlin-client/infrastructure/ResponseExtensions.kt.mustache @@ -0,0 +1,23 @@ +package {{packageName}}.infrastructure + +import okhttp3.Response + +/** + * Provides an extension to evaluation whether the response is a 1xx code + */ +val Response.isInformational : Boolean get() = this.code() in 100..199 + +/** + * Provides an extension to evaluation whether the response is a 3xx code + */ +val Response.isRedirect : Boolean get() = this.code() in 300..399 + +/** + * Provides an extension to evaluation whether the response is a 4xx code + */ +val Response.isClientError : Boolean get() = this.code() in 400..499 + +/** + * Provides an extension to evaluation whether the response is a 5xx (Standard) through 999 (non-standard) code + */ +val Response.isServerError : Boolean get() = this.code() in 500..999 \ No newline at end of file diff --git a/src/main/resources/v2/kotlin-client/infrastructure/Serializer.kt.mustache b/src/main/resources/v2/kotlin-client/infrastructure/Serializer.kt.mustache new file mode 100644 index 0000000000..71cb991ddb --- /dev/null +++ b/src/main/resources/v2/kotlin-client/infrastructure/Serializer.kt.mustache @@ -0,0 +1,14 @@ +package {{packageName}}.infrastructure + +import com.squareup.moshi.KotlinJsonAdapterFactory +import com.squareup.moshi.Moshi +import com.squareup.moshi.Rfc3339DateJsonAdapter +import java.util.* + +object Serializer { + @JvmStatic + val moshi: Moshi = Moshi.Builder() + .add(KotlinJsonAdapterFactory()) + .add(Date::class.java, Rfc3339DateJsonAdapter().nullSafe()) + .build() +} diff --git a/src/main/resources/v2/kotlin-client/licenseInfo.mustache b/src/main/resources/v2/kotlin-client/licenseInfo.mustache new file mode 100644 index 0000000000..aee680977d --- /dev/null +++ b/src/main/resources/v2/kotlin-client/licenseInfo.mustache @@ -0,0 +1,11 @@ +/** +* {{{appName}}} +* {{{appDescription}}} +* +* {{#version}}OpenAPI spec version: {{{version}}}{{/version}} +* {{#infoEmail}}Contact: {{{infoEmail}}}{{/infoEmail}} +* +* NOTE: This class is auto generated by the swagger code generator program. +* https://github.com/swagger-api/swagger-codegen.git +* Do not edit the class manually. +*/ \ No newline at end of file diff --git a/src/main/resources/v2/kotlin-client/model.mustache b/src/main/resources/v2/kotlin-client/model.mustache new file mode 100644 index 0000000000..bb27cf99c5 --- /dev/null +++ b/src/main/resources/v2/kotlin-client/model.mustache @@ -0,0 +1,14 @@ +{{>licenseInfo}} +package {{modelPackage}} + +{{#imports}}import {{import}} +{{/imports}} +{{#threetenbp}} +import org.threeten.bp.LocalDateTime +{{/threetenbp}} + +{{#models}} +{{#model}} +{{#isEnum}}{{>enum_class}}{{/isEnum}}{{^isEnum}}{{>data_class}}{{/isEnum}} +{{/model}} +{{/models}} diff --git a/src/main/resources/v2/kotlin-client/model_doc.mustache b/src/main/resources/v2/kotlin-client/model_doc.mustache new file mode 100644 index 0000000000..e3b7184211 --- /dev/null +++ b/src/main/resources/v2/kotlin-client/model_doc.mustache @@ -0,0 +1,3 @@ +{{#models}}{{#model}} +{{#isEnum}}{{>enum_doc}}{{/isEnum}}{{^isEnum}}{{>class_doc}}{{/isEnum}} +{{/model}}{{/models}} diff --git a/src/main/resources/v2/kotlin-client/settings.gradle.mustache b/src/main/resources/v2/kotlin-client/settings.gradle.mustache new file mode 100644 index 0000000000..448dc07602 --- /dev/null +++ b/src/main/resources/v2/kotlin-client/settings.gradle.mustache @@ -0,0 +1 @@ +rootProject.name = '{{artifactId}}' \ No newline at end of file diff --git a/src/main/resources/v2/kotlin-server/README.mustache b/src/main/resources/v2/kotlin-server/README.mustache new file mode 100644 index 0000000000..7b53516d38 --- /dev/null +++ b/src/main/resources/v2/kotlin-server/README.mustache @@ -0,0 +1,84 @@ +# {{packageName}} - Kotlin Server library for {{appName}} + +## Requires + +* Kotlin 1.1.2 +* Gradle 3.3 + +## Build + +First, create the gradle wrapper script: + +``` +gradle wrapper +``` + +Then, run: + +``` +./gradlew check assemble +``` + +This runs all tests and packages the library. + +## Features/Implementation Notes + +* Supports JSON inputs/outputs, File inputs, and Form inputs. +* Supports collection formats for query parameters: csv, tsv, ssv, pipes. +* Some Kotlin and Java types are fully qualified to avoid conflicts with types defined in Swagger definitions. + +{{#generateApiDocs}} + +## Documentation for API Endpoints + +All URIs are relative to *{{{basePath}}}* + +Class | Method | HTTP request | Description +------------ | ------------- | ------------- | ------------- +{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}*{{classname}}* | [**{{operationId}}**]({{apiDocPath}}{{classname}}.md#{{operationIdLowerCase}}) | **{{httpMethod}}** {{path}} | {{#summary}}{{{summary}}}{{/summary}} +{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}} +{{/generateApiDocs}} + +{{#generateModelDocs}} + +## Documentation for Models + +{{#modelPackage}} +{{#models}}{{#model}} - [{{{modelPackage}}}.{{{classname}}}]({{modelDocPath}}{{{classname}}}.md) +{{/model}}{{/models}} +{{/modelPackage}} +{{^modelPackage}} +No model defined in this package +{{/modelPackage}} +{{/generateModelDocs}} + +{{! TODO: optional documentation for authorization? }} +## Documentation for Authorization + +{{^authMethods}} +All endpoints do not require authorization. +{{/authMethods}} +{{#authMethods}} +{{#last}} +Authentication schemes defined for the API: +{{/last}} +{{/authMethods}} +{{#authMethods}} + +### {{name}} + +{{#isApiKey}}- **Type**: API key +- **API key parameter name**: {{keyParamName}} +- **Location**: {{#isKeyInQuery}}URL query string{{/isKeyInQuery}}{{#isKeyInHeader}}HTTP header{{/isKeyInHeader}} +{{/isApiKey}} +{{#isBasic}}- **Type**: HTTP basic authentication +{{/isBasic}} +{{#isOAuth}}- **Type**: OAuth +- **Flow**: {{flow}} +- **Authorization URL**: {{authorizationUrl}} +- **Scopes**: {{^scopes}}N/A{{/scopes}} +{{#scopes}} - {{scope}}: {{description}} +{{/scopes}} +{{/isOAuth}} + +{{/authMethods}} \ No newline at end of file diff --git a/src/main/resources/v2/kotlin-server/api_doc.mustache b/src/main/resources/v2/kotlin-server/api_doc.mustache new file mode 100644 index 0000000000..d288470ca8 --- /dev/null +++ b/src/main/resources/v2/kotlin-server/api_doc.mustache @@ -0,0 +1,65 @@ +# {{classname}}{{#description}} +{{description}}{{/description}} + +All URIs are relative to *{{basePath}}* + +Method | HTTP request | Description +------------- | ------------- | ------------- +{{#operations}}{{#operation}}[**{{operationId}}**]({{classname}}.md#{{operationId}}) | **{{httpMethod}}** {{path}} | {{#summary}}{{summary}}{{/summary}} +{{/operation}}{{/operations}} + +{{#operations}} +{{#operation}} + +# **{{operationId}}** +> {{#returnType}}{{returnType}} {{/returnType}}{{operationId}}({{#allParams}}{{{paramName}}}{{#hasMore}}, {{/hasMore}}{{/allParams}}) + +{{summary}}{{#notes}} + +{{notes}}{{/notes}} + +### Example +```kotlin +// Import classes: +//import {{{packageName}}}.infrastructure.* +//import {{{modelPackage}}}.* + +{{! TODO: Auth method documentation examples}} +val apiInstance = {{{classname}}}() +{{#allParams}} +val {{{paramName}}} : {{{dataType}}} = {{{example}}} // {{{dataType}}} | {{{description}}} +{{/allParams}} +try { + {{#returnType}}val result : {{{returnType}}} = {{/returnType}}apiInstance.{{{operationId}}}({{#allParams}}{{{paramName}}}{{#hasMore}}, {{/hasMore}}{{/allParams}}){{#returnType}} + println(result){{/returnType}} +} catch (e: ClientException) { + println("4xx response calling {{{classname}}}#{{{operationId}}}") + e.printStackTrace() +} catch (e: ServerException) { + println("5xx response calling {{{classname}}}#{{{operationId}}}") + e.printStackTrace() +} +``` + +### Parameters +{{^allParams}}This endpoint does not need any parameter.{{/allParams}}{{#allParams}}{{#-last}} +Name | Type | Description | Notes +------------- | ------------- | ------------- | -------------{{/-last}}{{/allParams}} +{{#allParams}} **{{paramName}}** | {{#isPrimitiveType}}**{{dataType}}**{{/isPrimitiveType}}{{^isPrimitiveType}}{{#isFile}}**{{dataType}}**{{/isFile}}{{^isFile}}{{#generateModelDocs}}[**{{dataType}}**]({{baseType}}.md){{/generateModelDocs}}{{^generateModelDocs}}**{{dataType}}**{{/generateModelDocs}}{{/isFile}}{{/isPrimitiveType}}| {{description}} |{{^required}} [optional]{{/required}}{{#defaultValue}} [default to {{defaultValue}}]{{/defaultValue}}{{#allowableValues}} [enum: {{#values}}{{{.}}}{{^-last}}, {{/-last}}{{/values}}]{{/allowableValues}} +{{/allParams}} + +### Return type + +{{#returnType}}{{#returnTypeIsPrimitive}}**{{returnType}}**{{/returnTypeIsPrimitive}}{{^returnTypeIsPrimitive}}{{#generateModelDocs}}[**{{returnType}}**]({{returnBaseType}}.md){{/generateModelDocs}}{{^generateModelDocs}}**{{returnType}}**{{/generateModelDocs}}{{/returnTypeIsPrimitive}}{{/returnType}}{{^returnType}}null (empty response body){{/returnType}} + +### Authorization + +{{^authMethods}}No authorization required{{/authMethods}}{{#authMethods}}[{{name}}](../README.md#{{name}}){{^-last}}, {{/-last}}{{/authMethods}} + +### HTTP request headers + + - **Content-Type**: {{#consumes}}{{{mediaType}}}{{#hasMore}}, {{/hasMore}}{{/consumes}}{{^consumes}}Not defined{{/consumes}} + - **Accept**: {{#produces}}{{{mediaType}}}{{#hasMore}}, {{/hasMore}}{{/produces}}{{^produces}}Not defined{{/produces}} + +{{/operation}} +{{/operations}} diff --git a/src/main/resources/v2/kotlin-server/class_doc.mustache b/src/main/resources/v2/kotlin-server/class_doc.mustache new file mode 100644 index 0000000000..45e1994273 --- /dev/null +++ b/src/main/resources/v2/kotlin-server/class_doc.mustache @@ -0,0 +1,15 @@ +# {{classname}} + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +{{#vars}}**{{name}}** | {{#isEnum}}[**inline**](#{{datatypeWithEnum}}){{/isEnum}}{{^isEnum}}{{#isPrimitiveType}}**{{datatype}}**{{/isPrimitiveType}}{{^isPrimitiveType}}[**{{datatype}}**]({{complexType}}.md){{/isPrimitiveType}}{{/isEnum}} | {{description}} | {{^required}} [optional]{{/required}}{{#readOnly}} [readonly]{{/readOnly}} +{{/vars}} +{{#vars}}{{#isEnum}} + +{{!NOTE: see java's resources "pojo_doc.mustache" once enums are fully implemented}} +## Enum: {{baseName}} +Name | Value +---- | -----{{#allowableValues}} +{{name}} | {{#values}}{{.}}{{^-last}}, {{/-last}}{{/values}}{{/allowableValues}} +{{/isEnum}}{{/vars}} diff --git a/src/main/resources/v2/kotlin-server/data_class.mustache b/src/main/resources/v2/kotlin-server/data_class.mustache new file mode 100644 index 0000000000..1237ec1f43 --- /dev/null +++ b/src/main/resources/v2/kotlin-server/data_class.mustache @@ -0,0 +1,25 @@ +/** + * {{{description}}} +{{#vars}} + * @param {{name}} {{{description}}} +{{/vars}} + */ +data class {{classname}} ( +{{#requiredVars}} +{{>data_class_req_var}}{{^-last}}, +{{/-last}}{{/requiredVars}}{{#hasRequired}}{{#hasOptional}}, +{{/hasOptional}}{{/hasRequired}}{{#optionalVars}}{{>data_class_opt_var}}{{^-last}}, +{{/-last}}{{/optionalVars}} +) { +{{#hasEnums}}{{#vars}}{{#isEnum}} + /** + * {{{description}}} + * Values: {{#allowableValues}}{{#enumVars}}{{&name}}{{^-last}},{{/-last}}{{/enumVars}}{{/allowableValues}} + */ + enum class {{nameInCamelCase}}(val value: {{dataType}}){ + {{#allowableValues}}{{#enumVars}} + {{&name}}({{{value}}}){{^-last}},{{/-last}}{{#-last}};{{/-last}} + {{/enumVars}}{{/allowableValues}} + } +{{/isEnum}}{{/vars}}{{/hasEnums}} +} diff --git a/src/main/resources/v2/kotlin-server/data_class_opt_var.mustache b/src/main/resources/v2/kotlin-server/data_class_opt_var.mustache new file mode 100644 index 0000000000..a88761ea90 --- /dev/null +++ b/src/main/resources/v2/kotlin-server/data_class_opt_var.mustache @@ -0,0 +1,4 @@ +{{#description}} + /* {{{description}}} */ +{{/description}} + val {{{name}}}: {{#isEnum}}{{classname}}.{{nameInCamelCase}}{{/isEnum}}{{^isEnum}}{{{datatype}}}{{/isEnum}}? = {{#defaultvalue}}{{defaultvalue}}{{/defaultvalue}}{{^defaultvalue}}null{{/defaultvalue}} \ No newline at end of file diff --git a/src/main/resources/v2/kotlin-server/data_class_req_var.mustache b/src/main/resources/v2/kotlin-server/data_class_req_var.mustache new file mode 100644 index 0000000000..8a33a15a18 --- /dev/null +++ b/src/main/resources/v2/kotlin-server/data_class_req_var.mustache @@ -0,0 +1,4 @@ +{{#description}} + /* {{{description}}} */ +{{/description}} + val {{{name}}}: {{#isEnum}}{{classname}}.{{nameInCamelCase}}{{/isEnum}}{{^isEnum}}{{{datatype}}}{{/isEnum}} \ No newline at end of file diff --git a/src/main/resources/v2/kotlin-server/enum_class.mustache b/src/main/resources/v2/kotlin-server/enum_class.mustache new file mode 100644 index 0000000000..791398b978 --- /dev/null +++ b/src/main/resources/v2/kotlin-server/enum_class.mustache @@ -0,0 +1,9 @@ +/** +* {{{description}}} +* Values: {{#allowableValues}}{{#enumVars}}{{&name}}{{^-last}},{{/-last}}{{/enumVars}}{{/allowableValues}} +*/ +enum class {{classname}}(val value: {{dataType}}){ +{{#allowableValues}}{{#enumVars}} + {{&name}}({{{value}}}){{^-last}},{{/-last}}{{#-last}};{{/-last}} +{{/enumVars}}{{/allowableValues}} +} diff --git a/src/main/resources/v2/kotlin-server/enum_doc.mustache b/src/main/resources/v2/kotlin-server/enum_doc.mustache new file mode 100644 index 0000000000..fcb3d7e61a --- /dev/null +++ b/src/main/resources/v2/kotlin-server/enum_doc.mustache @@ -0,0 +1,7 @@ +# {{classname}} + +## Enum + +{{#allowableValues}}{{#enumVars}} + * `{{name}}` (value: `{{{value}}}`) +{{/enumVars}}{{/allowableValues}} diff --git a/src/main/resources/v2/kotlin-server/libraries/ktor/ApiKeyAuth.kt.mustache b/src/main/resources/v2/kotlin-server/libraries/ktor/ApiKeyAuth.kt.mustache new file mode 100644 index 0000000000..39a0ea7a84 --- /dev/null +++ b/src/main/resources/v2/kotlin-server/libraries/ktor/ApiKeyAuth.kt.mustache @@ -0,0 +1,57 @@ +package {{packageName}}.infrastructure + +import io.ktor.application.ApplicationCall +import io.ktor.application.call +import io.ktor.auth.* +import io.ktor.request.ApplicationRequest +import io.ktor.response.respond + + +import io.ktor.application.* +import io.ktor.pipeline.* +import io.ktor.request.* +import io.ktor.response.* +import java.util.* + +enum class ApiKeyLocation(val location: String) { + QUERY("query"), + HEADER("header") +} +data class ApiKey(val value: String): Credential +data class ApiPrincipal(val apiKey: ApiKey?) : Principal +fun ApplicationCall.apiKey(key: String, keyLocation: ApiKeyLocation = ApiKeyLocation.valueOf("header")): ApiKey? = request.apiKey(key, keyLocation) +fun ApplicationRequest.apiKey(key: String, keyLocation: ApiKeyLocation = ApiKeyLocation.valueOf("header")): ApiKey? { + val value: String? = when(keyLocation) { + ApiKeyLocation.QUERY -> this.queryParameters[key] + ApiKeyLocation.HEADER -> this.headers[key] + } + when (value) { + null -> return null + else -> return ApiKey(value) + } +} + +fun AuthenticationPipeline.apiKeyAuth(apiKeyName: String, authLocation: String, validate: suspend (ApiKey) -> ApiPrincipal?) { + intercept(AuthenticationPipeline.RequestAuthentication) { context -> + val credentials = call.request.apiKey(apiKeyName, ApiKeyLocation.values().first { it.location == authLocation }) + val principal = credentials?.let { validate(it) } + + val cause = when { + credentials == null -> AuthenticationFailedCause.NoCredentials + principal == null -> AuthenticationFailedCause.InvalidCredentials + else -> null + } + + if (cause != null) { + context.challenge(apiKeyName, cause) { + // TODO: Verify correct response structure here. + call.respond(UnauthorizedResponse(HttpAuthHeader.Parameterized("API_KEY", mapOf("key" to apiKeyName), HeaderValueEncoding.QUOTED_ALWAYS))) + it.complete() + } + } + if (principal != null) { + context.principal(principal) + } + } +} + diff --git a/src/main/resources/v2/kotlin-server/libraries/ktor/AppMain.kt.mustache b/src/main/resources/v2/kotlin-server/libraries/ktor/AppMain.kt.mustache new file mode 100644 index 0000000000..53d4a8ba39 --- /dev/null +++ b/src/main/resources/v2/kotlin-server/libraries/ktor/AppMain.kt.mustache @@ -0,0 +1,74 @@ +package {{packageName}} + +import com.codahale.metrics.* +import com.typesafe.config.ConfigFactory +import io.ktor.application.* +import io.ktor.client.HttpClient +import io.ktor.client.engine.apache.Apache +import io.ktor.config.HoconApplicationConfig +import io.ktor.features.* +import io.ktor.gson.GsonConverter +import io.ktor.http.ContentType +import io.ktor.locations.* +import io.ktor.metrics.* +import io.ktor.routing.* +import java.util.concurrent.* +{{#generateApis}} +import {{apiPackage}}.* +{{/generateApis}} + +{{#imports}}import {{import}} +{{/imports}} + +internal val settings = HoconApplicationConfig(ConfigFactory.defaultApplication(HTTP::class.java.classLoader)) + +object HTTP { + val client = HttpClient(Apache) +} + +fun Application.main() { + install(DefaultHeaders) + install(Metrics) { + val reporter = Slf4jReporter.forRegistry(registry) + .outputTo(log) + .convertRatesTo(TimeUnit.SECONDS) + .convertDurationsTo(TimeUnit.MILLISECONDS) + .build() + reporter.start(10, TimeUnit.SECONDS) + } +{{#generateApis}} + install(ContentNegotiation) { + register(ContentType.Application.Json, GsonConverter()) + } + {{#featureAutoHead}} + install(AutoHeadResponse) // see http://ktor.io/features/autoheadresponse.html + {{/featureAutoHead}} + {{#featureConditionalHeaders}} + install(ConditionalHeaders) // see http://ktor.io/features/conditional-headers.html + {{/featureConditionalHeaders}} + {{#featureHSTS}} + install(HSTS, ApplicationHstsConfiguration()) // see http://ktor.io/features/hsts.html + {{/featureHSTS}} + {{#featureCORS}} + install(CORS, ApplicationCORSConfiguration()) // see http://ktor.io/features/cors.html + {{/featureCORS}} + {{#featureCompression}} + install(Compression, ApplicationCompressionConfiguration()) // see http://ktor.io/features/compression.html + {{/featureCompression}} + install(Locations) // see http://ktor.io/features/locations.html + install(Routing) { + {{#apiInfo}} + {{#apis}} + {{#operations}} + {{classname}}() + {{/operations}} + {{/apis}} + {{/apiInfo}} + } +{{/generateApis}} + + environment.monitor.subscribe(ApplicationStopping) + { + HTTP.client.close() + } +} \ No newline at end of file diff --git a/src/main/resources/v2/kotlin-server/libraries/ktor/Configuration.kt.mustache b/src/main/resources/v2/kotlin-server/libraries/ktor/Configuration.kt.mustache new file mode 100644 index 0000000000..1afaa5ff9e --- /dev/null +++ b/src/main/resources/v2/kotlin-server/libraries/ktor/Configuration.kt.mustache @@ -0,0 +1,108 @@ +package {{packageName}} + +// Use this file to hold package-level internal functions that return receiver object passed to the `install` method. +import io.ktor.auth.OAuthServerSettings +import io.ktor.features.* +import io.ktor.http.* +import java.time.Duration +import java.util.concurrent.Executors + +import {{packageName}}.settings + +{{#featureCORS}} +/** + * Application block for [CORS] configuration. + * + * This file may be excluded in .swagger-codegen-ignore, + * and application specific configuration can be applied in this function. + * + * See http://ktor.io/features/cors.html + */ +internal fun ApplicationCORSConfiguration(): CORS.Configuration.() -> Unit { + return { + // method(HttpMethod.Options) + // header(HttpHeaders.XForwardedProto) + // anyHost() + // host("my-host") + // host("my-host:80") + // host("my-host", subDomains = listOf("www")) + // host("my-host", schemes = listOf("http", "https")) + // allowCredentials = true + // maxAge = Duration.ofDays(1) + } +} +{{/featureCORS}} + +{{#featureHSTS}} +/** + * Application block for [HSTS] configuration. + * + * This file may be excluded in .swagger-codegen-ignore, + * and application specific configuration can be applied in this function. + * + * See http://ktor.io/features/hsts.html + */ +internal fun ApplicationHstsConfiguration(): HSTS.Configuration.() -> Unit { + return { + maxAge = Duration.ofDays(365) + includeSubDomains = true + preload = false + + // You may also apply any custom directives supported by specific user-agent. For example: + // customDirectives.put("redirectHttpToHttps", "false") + } +} +{{/featureHSTS}} + +{{#featureCompression}} +/** + * Application block for [Compression] configuration. + * + * This file may be excluded in .swagger-codegen-ignore, + * and application specific configuration can be applied in this function. + * + * See http://ktor.io/features/compression.html + */ +internal fun ApplicationCompressionConfiguration(): Compression.Configuration.() -> Unit { + return { + gzip { + priority = 1.0 + } + deflate { + priority = 10.0 + minimumSize(1024) // condition + } + } +} +{{/featureCompression}} + +// Defines authentication mechanisms used throughout the application. +val ApplicationAuthProviders: Map = listOf( +{{#authMethods}} + {{#isOAuth}} + OAuthServerSettings.OAuth2ServerSettings( + name = "{{name}}", + authorizeUrl = "{{authorizationUrl}}", + accessTokenUrl = "{{tokenUrl}}", + requestMethod = HttpMethod.Get, + {{! TODO: flow, doesn't seem to be supported yet by ktor }} + clientId = settings.property("auth.oauth.{{name}}.clientId").getString(), + clientSecret = settings.property("auth.oauth.{{name}}.clientSecret").getString(), + defaultScopes = listOf({{#scopes}}"{{scope}}"{{#hasMore}}, {{/hasMore}}{{/scopes}}) + ){{#hasMore}},{{/hasMore}} + {{/isOAuth}} +{{/authMethods}} +// OAuthServerSettings.OAuth2ServerSettings( +// name = "facebook", +// authorizeUrl = "https://graph.facebook.com/oauth/authorize", +// accessTokenUrl = "https://graph.facebook.com/oauth/access_token", +// requestMethod = HttpMethod.Post, +// +// clientId = "settings.property("auth.oauth.facebook.clientId").getString()", +// clientSecret = "settings.property("auth.oauth.facebook.clientSecret").getString()", +// defaultScopes = listOf("public_profile") +// ) +).associateBy { it.name } + +// Provides an application-level fixed thread pool on which to execute coroutines (mainly) +internal val ApplicationExecutors = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 4) diff --git a/src/main/resources/v2/kotlin-server/libraries/ktor/Dockerfile.mustache b/src/main/resources/v2/kotlin-server/libraries/ktor/Dockerfile.mustache new file mode 100644 index 0000000000..b9158ac26e --- /dev/null +++ b/src/main/resources/v2/kotlin-server/libraries/ktor/Dockerfile.mustache @@ -0,0 +1,7 @@ +FROM openjdk:8-jre-alpine + +COPY ./build/libs/{{artifactId}}.jar /root/{{artifactId}}.jar + +WORKDIR /root + +CMD ["java", "-server", "-Xms4g", "-Xmx4g", "-XX:+UseG1GC", "-XX:MaxGCPauseMillis=100", "-XX:+UseStringDeduplication", "-jar", "{{artifactId}}.jar"] \ No newline at end of file diff --git a/src/main/resources/v2/kotlin-server/libraries/ktor/Paths.kt.mustache b/src/main/resources/v2/kotlin-server/libraries/ktor/Paths.kt.mustache new file mode 100644 index 0000000000..a4a4bb7a10 --- /dev/null +++ b/src/main/resources/v2/kotlin-server/libraries/ktor/Paths.kt.mustache @@ -0,0 +1,43 @@ +{{>licenseInfo}} +package {{packageName}} + +import io.ktor.application.ApplicationCall +import io.ktor.http.HttpMethod +import io.ktor.locations.* +import io.ktor.pipeline.PipelineContext +import io.ktor.routing.Route +import io.ktor.routing.method +import {{modelPackage}}.* + +{{#imports}} +import {{import}} +{{/imports}} + +// NOTE: ktor-location@0.9.0 is missing extension for Route.delete. This includes it. +inline fun Route.delete(noinline body: suspend PipelineContext.(T) -> Unit): Route { + return location(T::class) { + method(HttpMethod.Delete) { + handle(body) + } + } +} + +{{#apiInfo}} +object Paths { +{{#apis}} +{{#operations}} + {{#operation}} + {{^bodyAllowed}} + /** + * {{summary}} + * {{#unescapedNotes}}{{.}}{{/unescapedNotes}} + {{#allParams}}* @param {{paramName}} {{description}} {{^required}}(optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}} + {{/allParams}}*/ + @Location("{{path}}") class {{operationId}}({{#allParams}}val {{paramName}}: {{{dataType}}}{{#hasMore}}, {{/hasMore}}{{/allParams}}) + + {{/bodyAllowed}} + {{/operation}} +{{/operations}} +{{/apis}} +} +{{/apiInfo}} diff --git a/src/main/resources/v2/kotlin-server/libraries/ktor/README.mustache b/src/main/resources/v2/kotlin-server/libraries/ktor/README.mustache new file mode 100644 index 0000000000..0b7ca34e4b --- /dev/null +++ b/src/main/resources/v2/kotlin-server/libraries/ktor/README.mustache @@ -0,0 +1,101 @@ +# {{packageName}} - Kotlin Server library for {{appName}} + +{{#unescapedAppDescription}} +{{.}} +{{/unescapedAppDescription}} + +Generated by Swagger Codegen {{generatorVersion}} ({{generatedDate}}). + +## Requires + +* Kotlin 1.2.10 +* Gradle 4.3 + +## Build + +First, create the gradle wrapper script: + +``` +gradle wrapper +``` + +Then, run: + +``` +./gradlew check assemble +``` + +This runs all tests and packages the library. + +## Running + +The server builds as a fat jar with a main entrypoint. To start the service, run `java -jar ./build/libs/{{artifactId}}.jar`. + +You may also run in docker: + +``` +docker build -t {{artifactId}} . +docker run -p 8080:8080 {{artifactId}} +``` + +## Features/Implementation Notes + +* Supports JSON inputs/outputs, File inputs, and Form inputs (see ktor documentation for more info). +* ~Supports collection formats for query parameters: csv, tsv, ssv, pipes.~ +* Some Kotlin and Java types are fully qualified to avoid conflicts with types defined in Swagger definitions. + +{{#generateApiDocs}} + +## Documentation for API Endpoints + +All URIs are relative to *{{{basePath}}}* + +Class | Method | HTTP request | Description +------------ | ------------- | ------------- | ------------- +{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}*{{classname}}* | [**{{operationId}}**]({{apiDocPath}}{{classname}}.md#{{operationIdLowerCase}}) | **{{httpMethod}}** {{path}} | {{#summary}}{{{summary}}}{{/summary}} +{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}} +{{/generateApiDocs}} + +{{#generateModelDocs}} + +## Documentation for Models + +{{#modelPackage}} +{{#models}}{{#model}} - [{{{modelPackage}}}.{{{classname}}}]({{modelDocPath}}{{{classname}}}.md) +{{/model}}{{/models}} +{{/modelPackage}} +{{^modelPackage}} +No model defined in this package +{{/modelPackage}} +{{/generateModelDocs}} + +{{! TODO: optional documentation for authorization? }} +## Documentation for Authorization + +{{^authMethods}} +All endpoints do not require authorization. +{{/authMethods}} +{{#authMethods}} +{{#last}} +Authentication schemes defined for the API: +{{/last}} +{{/authMethods}} +{{#authMethods}} + +### {{name}} + +{{#isApiKey}}- **Type**: API key +- **API key parameter name**: {{keyParamName}} +- **Location**: {{#isKeyInQuery}}URL query string{{/isKeyInQuery}}{{#isKeyInHeader}}HTTP header{{/isKeyInHeader}} +{{/isApiKey}} +{{#isBasic}}- **Type**: HTTP basic authentication +{{/isBasic}} +{{#isOAuth}}- **Type**: OAuth +- **Flow**: {{flow}} +- **Authorization URL**: {{authorizationUrl}} +- **Scopes**: {{^scopes}}N/A{{/scopes}} +{{#scopes}} - {{scope}}: {{description}} +{{/scopes}} +{{/isOAuth}} + +{{/authMethods}} \ No newline at end of file diff --git a/src/main/resources/v2/kotlin-server/libraries/ktor/_api_body.mustache b/src/main/resources/v2/kotlin-server/libraries/ktor/_api_body.mustache new file mode 100644 index 0000000000..9dcf500f49 --- /dev/null +++ b/src/main/resources/v2/kotlin-server/libraries/ktor/_api_body.mustache @@ -0,0 +1,25 @@ +{{#hasAuthMethods}} +{{>libraries/ktor/_principal}} +if (principal == null) { + call.respond(HttpStatusCode.Unauthorized) +} else { + {{#examples}} + {{#-first}} + {{#lambda.indented}}{{>_response}}{{/lambda.indented}} + {{/-first}} + {{/examples}} + {{^examples}} + call.respond(HttpStatusCode.NotImplemented) + {{/examples}} +} +{{/hasAuthMethods}} +{{^hasAuthMethods}} +{{#examples}} +{{#-first}} +{{>libraries/ktor/_response}} +{{/-first}} +{{/examples}} +{{^examples}} +call.respond(HttpStatusCode.NotImplemented) +{{/examples}} +{{/hasAuthMethods}} \ No newline at end of file diff --git a/src/main/resources/v2/kotlin-server/libraries/ktor/_principal.mustache b/src/main/resources/v2/kotlin-server/libraries/ktor/_principal.mustache new file mode 100644 index 0000000000..d49c5d537f --- /dev/null +++ b/src/main/resources/v2/kotlin-server/libraries/ktor/_principal.mustache @@ -0,0 +1,11 @@ +{{#authMethods}} +{{#isBasic}} +val principal = call.authentication.principal() +{{/isBasic}} +{{#isApiKey}} +val principal = call.authentication.principal() +{{/isApiKey}} +{{#isOAuth}} +val principal = call.authentication.principal() +{{/isOAuth}} +{{/authMethods}} diff --git a/src/main/resources/v2/kotlin-server/libraries/ktor/_response.mustache b/src/main/resources/v2/kotlin-server/libraries/ktor/_response.mustache new file mode 100644 index 0000000000..fb26fb4a7f --- /dev/null +++ b/src/main/resources/v2/kotlin-server/libraries/ktor/_response.mustache @@ -0,0 +1,8 @@ +val exampleContentType = "{{{contentType}}}" +val exampleContentString = """{{&example}}""" + +when(exampleContentType) { + "application/json" -> call.respond(gson.fromJson(exampleContentString, empty::class.java)) + "application/xml" -> call.respondText(exampleContentString, ContentType.Text.Xml) + else -> call.respondText(exampleContentString) +} \ No newline at end of file diff --git a/src/main/resources/v2/kotlin-server/libraries/ktor/api.mustache b/src/main/resources/v2/kotlin-server/libraries/ktor/api.mustache new file mode 100644 index 0000000000..16b6b9fbf6 --- /dev/null +++ b/src/main/resources/v2/kotlin-server/libraries/ktor/api.mustache @@ -0,0 +1,114 @@ +{{>licenseInfo}} +package {{apiPackage}} + +import com.google.gson.Gson +import io.ktor.application.call +import io.ktor.auth.UserIdPrincipal +import io.ktor.auth.authentication +import io.ktor.auth.basicAuthentication +import io.ktor.auth.oauth +import io.ktor.auth.OAuthAccessTokenResponse +import io.ktor.auth.OAuthServerSettings +import io.ktor.http.ContentType +import io.ktor.http.HttpStatusCode +import io.ktor.locations.* +import io.ktor.response.respond +import io.ktor.response.respondText +import io.ktor.routing.* + +import kotlinx.coroutines.experimental.asCoroutineDispatcher + +import {{packageName}}.ApplicationAuthProviders +import {{packageName}}.Paths +import {{packageName}}.ApplicationExecutors +import {{packageName}}.HTTP.client +import {{packageName}}.infrastructure.ApiPrincipal +import {{packageName}}.infrastructure.apiKeyAuth + +// ktor 0.9.x is missing io.ktor.locations.DELETE, this adds it. +// see https://github.com/ktorio/ktor/issues/288 +import {{packageName}}.delete + +{{#imports}}import {{import}} +{{/imports}} + +{{#operations}} +fun Route.{{classname}}() { + val gson = Gson() + val empty = mutableMapOf() +{{#operation}} + {{#bodyAllowed}} + + route("{{path}}") { + {{#lambda.lowercase}}{{httpMethod}}{{/lambda.lowercase}} { + {{#lambda.indented_12}}{{>libraries/ktor/_api_body}}{{/lambda.indented_12}} + } + } + {{/bodyAllowed}} + {{^bodyAllowed}} + + {{! NOTE: Locations can be used on routes without body parameters.}} + {{#lambda.lowercase}}{{httpMethod}}{{/lambda.lowercase}} { it: Paths.{{operationId}} -> + {{#lambda.indented_8}}{{>libraries/ktor/_api_body}}{{/lambda.indented_8}} + } + {{/bodyAllowed}} + {{! THis looks a little weird, but it's completely valid Kotlin code, and simplifies templated route logic above. }} + {{#hasAuthMethods}}.apply { + // TODO: ktor doesn't allow different authentication registrations for endpoints sharing the same path but different methods. + // It could be the authentication block is being abused here. Until this is resolved, swallow duplicate exceptions. + + try { + authentication { + {{#authMethods}} + {{#isBasic}} + basicAuthentication("{{{name}}}") { credentials -> + // TODO: "Apply your basic authentication functionality." + // Accessible in-method via call.principal() + if (credentials.name == "Swagger" && "Codegen" == credentials.password) { + UserIdPrincipal(credentials.name) + } else { + null + } + } + {{/isBasic}} + {{#isApiKey}} + // "Implement API key auth ({{{name}}}) for parameter name '{{{keyParamName}}}'." + apiKeyAuth("{{{keyParamName}}}", {{#isKeyInQuery}}"query"{{/isKeyInQuery}}{{#isKeyInHeader}}"header"{{/isKeyInHeader}}) { + // TODO: "Verify key here , accessible as it.value" + if (it.value == "keyboardcat") { + ApiPrincipal(it) + } else { + null + } + } + {{/isApiKey}} + {{#isOAuth}} + {{#bodyAllowed}} + oauth(client, ApplicationExecutors.asCoroutineDispatcher(), { ApplicationAuthProviders["{{{name}}}"] }, { + // TODO: define a callback url here. + "/" + }) + {{/bodyAllowed}} + {{^bodyAllowed}} + oauthAtLocation(client, ApplicationExecutors.asCoroutineDispatcher(), + providerLookup = { ApplicationAuthProviders["{{{name}}}"] }, + urlProvider = { currentLocation, provider -> + // TODO: define a callback url here. + "/" + }) + {{/bodyAllowed}} + {{/isOAuth}} + {{/authMethods}} + } + } catch(e: io.ktor.application.DuplicateApplicationFeatureException){ + application.environment.log.warn("authentication block for '{{path}}' is duplicated in code. " + + "Generated endpoints may need to be merged under a 'route' entry.") + } + } + {{/hasAuthMethods}} + {{^hasAuthMethods}} + + {{/hasAuthMethods}} +{{/operation}} +} +{{/operations}} \ No newline at end of file diff --git a/src/main/resources/v2/kotlin-server/libraries/ktor/application.conf.mustache b/src/main/resources/v2/kotlin-server/libraries/ktor/application.conf.mustache new file mode 100644 index 0000000000..032be42fa1 --- /dev/null +++ b/src/main/resources/v2/kotlin-server/libraries/ktor/application.conf.mustache @@ -0,0 +1,27 @@ +ktor { + deployment { + environment = development + port = 8080 + autoreload = true + watch = [ {{packageName}} ] + } + + application { + modules = [ {{packageName}}.AppMainKt.main ] + } +} + +# Typesafe config allows multiple ways to provide configuration values without hard-coding them here. +# Please see https://github.com/lightbend/config for details. +auth { + oauth { +{{#authMethods}} +{{#isOAuth}} + {{name}} { + clientId = "" + clientSecret = "" + } +{{/isOAuth}} +{{/authMethods}} + } +} \ No newline at end of file diff --git a/src/main/resources/v2/kotlin-server/libraries/ktor/build.gradle.mustache b/src/main/resources/v2/kotlin-server/libraries/ktor/build.gradle.mustache new file mode 100644 index 0000000000..494ea0e76f --- /dev/null +++ b/src/main/resources/v2/kotlin-server/libraries/ktor/build.gradle.mustache @@ -0,0 +1,74 @@ +group '{{groupId}}' +version '{{artifactVersion}}' + +task wrapper(type: Wrapper) { + gradleVersion = '4.3' + distributionUrl = "https://services.gradle.org/distributions/gradle-$gradleVersion-all.zip" +} + +buildscript { + ext.kotlin_version = '1.2.10' + ext.ktor_version = '0.9.1-alpha-9' + ext.shadow_version = '2.0.2' + + repositories { + mavenCentral() + maven { + url "https://plugins.gradle.org/m2/" + } + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "com.github.jengelman.gradle.plugins:shadow:$shadow_version" + } +} + +apply plugin: 'java' +apply plugin: 'kotlin' +apply plugin: 'application' + +mainClassName = "io.ktor.server.netty.DevelopmentEngine" + +// Initialization order with shadow 2.0.1 and Gradle 4.3 is weird. +// See https://github.com/johnrengelman/shadow/issues/336#issuecomment-355402508 +apply plugin: 'com.github.johnrengelman.shadow' + +sourceCompatibility = 1.8 + +compileKotlin { + kotlinOptions.jvmTarget = "1.8" +} + +compileTestKotlin { + kotlinOptions.jvmTarget = "1.8" +} + +kotlin { + experimental { + coroutines "enable" + } +} + +shadowJar { + baseName = '{{artifactId}}' + classifier = null + version = null +} + +repositories { + mavenCentral() + maven { url "http://dl.bintray.com/kotlin/ktor" } + maven { url "https://dl.bintray.com/kotlin/kotlinx" } +} + +dependencies { + compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" + compile "io.ktor:ktor-server-netty:$ktor_version" + compile "io.ktor:ktor-metrics:$ktor_version" + compile "io.ktor:ktor-locations:$ktor_version" + compile "io.ktor:ktor-gson:$ktor_version" + compile "io.ktor:ktor-client-core:$ktor_version" + compile "io.ktor:ktor-client-apache:$ktor_version" + compile "ch.qos.logback:logback-classic:1.2.1" + testCompile group: 'junit', name: 'junit', version: '4.12' +} diff --git a/src/main/resources/v2/kotlin-server/libraries/ktor/gradle.properties b/src/main/resources/v2/kotlin-server/libraries/ktor/gradle.properties new file mode 100644 index 0000000000..5f1ed7bbe0 --- /dev/null +++ b/src/main/resources/v2/kotlin-server/libraries/ktor/gradle.properties @@ -0,0 +1 @@ +org.gradle.caching=true \ No newline at end of file diff --git a/src/main/resources/v2/kotlin-server/libraries/ktor/licenseInfo.mustache b/src/main/resources/v2/kotlin-server/libraries/ktor/licenseInfo.mustache new file mode 100644 index 0000000000..aee680977d --- /dev/null +++ b/src/main/resources/v2/kotlin-server/libraries/ktor/licenseInfo.mustache @@ -0,0 +1,11 @@ +/** +* {{{appName}}} +* {{{appDescription}}} +* +* {{#version}}OpenAPI spec version: {{{version}}}{{/version}} +* {{#infoEmail}}Contact: {{{infoEmail}}}{{/infoEmail}} +* +* NOTE: This class is auto generated by the swagger code generator program. +* https://github.com/swagger-api/swagger-codegen.git +* Do not edit the class manually. +*/ \ No newline at end of file diff --git a/src/main/resources/v2/kotlin-server/libraries/ktor/logback.xml b/src/main/resources/v2/kotlin-server/libraries/ktor/logback.xml new file mode 100644 index 0000000000..d0eaba8deb --- /dev/null +++ b/src/main/resources/v2/kotlin-server/libraries/ktor/logback.xml @@ -0,0 +1,15 @@ + + + + %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + diff --git a/src/main/resources/v2/kotlin-server/licenseInfo.mustache b/src/main/resources/v2/kotlin-server/licenseInfo.mustache new file mode 100644 index 0000000000..aee680977d --- /dev/null +++ b/src/main/resources/v2/kotlin-server/licenseInfo.mustache @@ -0,0 +1,11 @@ +/** +* {{{appName}}} +* {{{appDescription}}} +* +* {{#version}}OpenAPI spec version: {{{version}}}{{/version}} +* {{#infoEmail}}Contact: {{{infoEmail}}}{{/infoEmail}} +* +* NOTE: This class is auto generated by the swagger code generator program. +* https://github.com/swagger-api/swagger-codegen.git +* Do not edit the class manually. +*/ \ No newline at end of file diff --git a/src/main/resources/v2/kotlin-server/model.mustache b/src/main/resources/v2/kotlin-server/model.mustache new file mode 100644 index 0000000000..780dd84b97 --- /dev/null +++ b/src/main/resources/v2/kotlin-server/model.mustache @@ -0,0 +1,11 @@ +{{>licenseInfo}} +package {{modelPackage}} + +{{#imports}}import {{import}} +{{/imports}} + +{{#models}} +{{#model}} +{{#isEnum}}{{>enum_class}}{{/isEnum}}{{^isEnum}}{{>data_class}}{{/isEnum}} +{{/model}} +{{/models}} diff --git a/src/main/resources/v2/kotlin-server/model_doc.mustache b/src/main/resources/v2/kotlin-server/model_doc.mustache new file mode 100644 index 0000000000..e3b7184211 --- /dev/null +++ b/src/main/resources/v2/kotlin-server/model_doc.mustache @@ -0,0 +1,3 @@ +{{#models}}{{#model}} +{{#isEnum}}{{>enum_doc}}{{/isEnum}}{{^isEnum}}{{>class_doc}}{{/isEnum}} +{{/model}}{{/models}} diff --git a/src/main/resources/v2/kotlin-server/settings.gradle.mustache b/src/main/resources/v2/kotlin-server/settings.gradle.mustache new file mode 100644 index 0000000000..448dc07602 --- /dev/null +++ b/src/main/resources/v2/kotlin-server/settings.gradle.mustache @@ -0,0 +1 @@ +rootProject.name = '{{artifactId}}' \ No newline at end of file diff --git a/src/test/java/io/swagger/codegen/languages/kotlin/KotlinClientCodegenModelTest.java b/src/test/java/io/swagger/codegen/languages/kotlin/KotlinClientCodegenModelTest.java new file mode 100644 index 0000000000..4e1c9864ba --- /dev/null +++ b/src/test/java/io/swagger/codegen/languages/kotlin/KotlinClientCodegenModelTest.java @@ -0,0 +1,276 @@ +package io.swagger.codegen.languages.kotlin; + +import io.swagger.codegen.*; +import io.swagger.codegen.languages.kotlin.KotlinClientCodegen; +import io.swagger.models.*; +import io.swagger.models.properties.*; + +import io.swagger.v3.oas.models.media.ArraySchema; +import io.swagger.v3.oas.models.media.BooleanSchema; +import io.swagger.v3.oas.models.media.ByteArraySchema; +import io.swagger.v3.oas.models.media.DateTimeSchema; +import io.swagger.v3.oas.models.media.IntegerSchema; +import io.swagger.v3.oas.models.media.MapSchema; +import io.swagger.v3.oas.models.media.NumberSchema; +import io.swagger.v3.oas.models.media.Schema; +import io.swagger.v3.oas.models.media.StringSchema; + +import io.swagger.v3.parser.util.SchemaTypeUtil; + +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import static io.swagger.codegen.languages.helpers.ExtensionHelper.getBooleanValue; + +@SuppressWarnings("static-method") +public class KotlinClientCodegenModelTest { + + private Schema getArrayTestSchema() { + // Schema propertySchema = new ArraySchema() + // .items(new Schema().$ref("#/components/schemas/Child")) + // .description("an array property"); + // propertySchema.addExtension("x-item-name", "child"); + return new ArraySchema() + .items(new StringSchema()) + .description("a sample model") + .name("arraySchema"); + } + + private Schema getSimpleSchema() { + + return new Schema() + .description("a sample model") + .addProperties("id", new IntegerSchema() + .format(SchemaTypeUtil.INTEGER64_FORMAT)) + .addProperties("name", new StringSchema() + .example("Tony")) + .addProperties("createdAt", new DateTimeSchema()) + .addRequiredItem("id") + .addRequiredItem("name"); + } + + private Schema getMapSchema() { + return new Schema() + .description("a sample model") + .additionalProperties(new Schema().$ref("#/components/schemas/Children")); + } + + private Schema getComplexSchema() { + return new Schema() + .description("a sample model") + .additionalProperties(new Schema().$ref("#/definitions/Child")); + } + + @Test(description = "convert a simple model") + public void simpleModelTest() { + // final Model model = getSimpleModel(); + final Schema schema = getSimpleSchema(); + final CodegenConfig codegen = new KotlinClientCodegen(); + + final CodegenModel cm = codegen.fromModel("sample", schema); + + Assert.assertEquals(cm.name, "sample"); + Assert.assertEquals(cm.classname, "Sample"); + Assert.assertEquals(cm.description, "a sample model"); + Assert.assertEquals(cm.vars.size(), 3); + + final CodegenProperty property1 = cm.vars.get(0); + Assert.assertEquals(property1.baseName, "id"); + Assert.assertEquals(property1.datatype, "kotlin.Long"); + Assert.assertEquals(property1.name, "id"); + Assert.assertEquals(property1.defaultValue, "null"); + Assert.assertEquals(property1.baseType, "kotlin.Long"); + Assert.assertTrue(getBooleanValue(property1, CodegenConstants.HAS_MORE_EXT_NAME)); + Assert.assertTrue(property1.required); + Assert.assertTrue(getBooleanValue(property1, CodegenConstants.IS_PRIMITIVE_TYPE_EXT_NAME)); + Assert.assertTrue(getBooleanValue(property1, CodegenConstants.IS_NOT_CONTAINER_EXT_NAME)); + + final CodegenProperty property2 = cm.vars.get(1); + Assert.assertEquals(property2.baseName, "name"); + Assert.assertEquals(property2.datatype, "kotlin.String"); + Assert.assertEquals(property2.name, "name"); + Assert.assertEquals(property2.defaultValue, "null"); + Assert.assertEquals(property2.baseType, "kotlin.String"); + Assert.assertTrue(getBooleanValue(property2, CodegenConstants.HAS_MORE_EXT_NAME)); + Assert.assertTrue(property2.required); + Assert.assertTrue(getBooleanValue(property2, CodegenConstants.IS_PRIMITIVE_TYPE_EXT_NAME)); + Assert.assertTrue(getBooleanValue(property2, CodegenConstants.IS_NOT_CONTAINER_EXT_NAME)); + + final CodegenProperty property3 = cm.vars.get(2); + Assert.assertEquals(property3.baseName, "createdAt"); + Assert.assertEquals(property3.datatype, "java.time.LocalDateTime"); + Assert.assertEquals(property3.name, "createdAt"); + Assert.assertEquals(property3.defaultValue, "null"); + Assert.assertEquals(property3.baseType, "java.time.LocalDateTime"); + Assert.assertTrue(getBooleanValue(property3, CodegenConstants.HAS_MORE_EXT_NAME)); + Assert.assertFalse(property3.required); + Assert.assertTrue(getBooleanValue(property3, CodegenConstants.IS_NOT_CONTAINER_EXT_NAME)); + } + + @Test(description = "convert a simple model: threetenbp") + public void selectDateLibraryAsThreetenbp() { + final Schema schema = getSimpleSchema(); + final KotlinClientCodegen codegen = new KotlinClientCodegen(); + codegen.setDateLibrary(KotlinClientCodegen.DateLibrary.THREETENBP.value); + codegen.processOpts(); + + final CodegenModel cm = codegen.fromModel("sample", schema); + + final CodegenProperty property3 = cm.vars.get(2); + Assert.assertEquals(property3.baseName, "createdAt"); + Assert.assertEquals(property3.datatype, "org.threeten.bp.LocalDateTime"); + Assert.assertEquals(property3.name, "createdAt"); + Assert.assertEquals(property3.defaultValue, "null"); + Assert.assertEquals(property3.baseType, "org.threeten.bp.LocalDateTime"); + Assert.assertTrue(getBooleanValue(property3, CodegenConstants.HAS_MORE_EXT_NAME)); + Assert.assertFalse(property3.required); + Assert.assertTrue(getBooleanValue(property3, CodegenConstants.IS_NOT_CONTAINER_EXT_NAME)); + } + + @Test(description = "convert a simple model: date string") + public void selectDateLibraryAsString() { + final Schema schema = getSimpleSchema(); + final KotlinClientCodegen codegen = new KotlinClientCodegen(); + codegen.setDateLibrary(KotlinClientCodegen.DateLibrary.STRING.value); + codegen.processOpts(); + + final CodegenModel cm = codegen.fromModel("sample", schema); + + final CodegenProperty property3 = cm.vars.get(2); + Assert.assertEquals(property3.baseName, "createdAt"); + Assert.assertEquals(property3.datatype, "kotlin.String"); + Assert.assertEquals(property3.name, "createdAt"); + Assert.assertEquals(property3.defaultValue, "null"); + Assert.assertEquals(property3.baseType, "kotlin.String"); + Assert.assertTrue(getBooleanValue(property3, CodegenConstants.HAS_MORE_EXT_NAME)); + Assert.assertFalse(property3.required); + Assert.assertTrue(getBooleanValue(property3, CodegenConstants.IS_NOT_CONTAINER_EXT_NAME)); + } + + @Test(description = "convert a simple model: date java8") + public void selectDateLibraryAsJava8() { + final Schema schema = getSimpleSchema(); + final KotlinClientCodegen codegen = new KotlinClientCodegen(); + codegen.setDateLibrary(KotlinClientCodegen.DateLibrary.JAVA8.value); + codegen.processOpts(); + + final CodegenModel cm = codegen.fromModel("sample", schema); + + final CodegenProperty property3 = cm.vars.get(2); + Assert.assertEquals(property3.baseName, "createdAt"); + Assert.assertEquals(property3.datatype, "java.time.LocalDateTime"); + Assert.assertEquals(property3.name, "createdAt"); + Assert.assertEquals(property3.defaultValue, "null"); + Assert.assertEquals(property3.baseType, "java.time.LocalDateTime"); + Assert.assertTrue(getBooleanValue(property3, CodegenConstants.HAS_MORE_EXT_NAME)); + Assert.assertFalse(property3.required); + Assert.assertTrue(getBooleanValue(property3, CodegenConstants.IS_NOT_CONTAINER_EXT_NAME)); + } + + @Test(description = "convert a model with array property to default kotlin.Array") + public void arrayPropertyTest() { + final Schema schema = getArrayTestSchema(); + + final CodegenConfig codegen = new KotlinClientCodegen(); + final CodegenModel generated = codegen.fromModel("sample", schema); + + System.err.println(generated.toString()); + + Assert.assertEquals(generated.name, "sample"); + Assert.assertEquals(generated.classname, "Sample"); + Assert.assertEquals(generated.description, "a sample model"); + Assert.assertEquals(generated.vars.size(), 0); + + // final CodegenProperty property = generated.vars.get(0); + // Assert.assertEquals(property.baseName, "examples"); + // Assert.assertEquals(property.getter, "getExamples"); + // Assert.assertEquals(property.setter, "setExamples"); + // // Assert.assertEquals(property.datatype, "kotlin.Array"); + // Assert.assertEquals(property.name, "examples"); + // Assert.assertEquals(property.defaultValue, "null"); + // Assert.assertEquals(property.baseType, "kotlin.Array"); + // Assert.assertEquals(property.containerType, "array"); + // Assert.assertFalse(property.required); + // Assert.assertTrue(getBooleanValue(property, CodegenConstants.IS_CONTAINER_EXT_NAME)); + } + + @Test(description = "convert a model with a map property") + public void mapPropertyTest() { + final Schema schema = getMapSchema(); + final CodegenConfig codegen = new KotlinClientCodegen(); + final CodegenModel cm = codegen.fromModel("sample", schema); + + Assert.assertEquals(cm.name, "sample"); + Assert.assertEquals(cm.classname, "Sample"); + Assert.assertEquals(cm.description, "a sample model"); + Assert.assertEquals(cm.vars.size(), 1); + + final CodegenProperty property1 = cm.vars.get(0); + Assert.assertEquals(property1.baseName, "mapping"); + Assert.assertEquals(property1.datatype, "kotlin.collections.Map"); + Assert.assertEquals(property1.name, "mapping"); + Assert.assertEquals(property1.baseType, "kotlin.collections.Map"); + Assert.assertEquals(property1.containerType, "map"); + Assert.assertFalse(property1.required); + Assert.assertTrue(getBooleanValue(property1, CodegenConstants.IS_CONTAINER_EXT_NAME)); + Assert.assertTrue(getBooleanValue(property1, CodegenConstants.IS_PRIMITIVE_TYPE_EXT_NAME)); + } + + @Test(description = "convert a model with complex property") + public void complexPropertyTest() { + final Schema schema = getComplexSchema(); + final CodegenConfig codegen = new KotlinClientCodegen(); + final CodegenModel cm = codegen.fromModel("sample", schema); + + Assert.assertEquals(cm.name, "sample"); + Assert.assertEquals(cm.classname, "Sample"); + Assert.assertEquals(cm.description, "a sample model"); + Assert.assertEquals(cm.vars.size(), 1); + + final CodegenProperty property1 = cm.vars.get(0); + Assert.assertEquals(property1.baseName, "child"); + Assert.assertEquals(property1.datatype, "Child"); + Assert.assertEquals(property1.name, "child"); + Assert.assertEquals(property1.baseType, "Child"); + Assert.assertFalse(property1.required); + Assert.assertTrue(getBooleanValue(property1, CodegenConstants.IS_NOT_CONTAINER_EXT_NAME)); + } + + @DataProvider(name = "modelNames") + public static Object[][] modelNames() { + return new Object[][]{ + {"TestNs.TestClass", new ModelNameTest("TestNs.TestClass", "TestNsTestClass")}, + {"$", new ModelNameTest("$", "Dollar")}, + {"for", new ModelNameTest("`for`", "`for`")}, + {"One createOptions() { + ImmutableMap.Builder builder = new ImmutableMap.Builder(); + return builder + .put(CodegenConstants.PACKAGE_NAME, PACKAGE_NAME_VALUE) + .put(CodegenConstants.ARTIFACT_VERSION, ARTIFACT_VERSION_VALUE) + .put(CodegenConstants.ARTIFACT_ID, ARTIFACT_ID) + .put(CodegenConstants.GROUP_ID, GROUP_ID) + .put(CodegenConstants.SOURCE_FOLDER, SOURCE_FOLDER) + .put(CodegenConstants.ENUM_PROPERTY_NAMING, ENUM_PROPERTY_NAMING) + .put("dateLibrary", DATE_LIBRARY) + .build(); + } + + @Override + public boolean isServer() { + return false; + } +} + From 25e14478c9da7498e52d64a46b36ac5de65aa18f Mon Sep 17 00:00:00 2001 From: tcslater Date: Wed, 21 Feb 2018 12:12:21 +1100 Subject: [PATCH 02/34] Updated templates to work with handlebars --- src/main/resources/v2/kotlin-client/api_doc.mustache | 8 ++++---- .../resources/v2/kotlin-client/class_doc.mustache | 2 +- .../resources/v2/kotlin-client/data_class.mustache | 12 ++++++------ .../resources/v2/kotlin-client/enum_class.mustache | 4 ++-- src/main/resources/v2/kotlin-server/api_doc.mustache | 8 ++++---- .../resources/v2/kotlin-server/class_doc.mustache | 2 +- .../resources/v2/kotlin-server/data_class.mustache | 12 ++++++------ .../resources/v2/kotlin-server/enum_class.mustache | 4 ++-- 8 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/main/resources/v2/kotlin-client/api_doc.mustache b/src/main/resources/v2/kotlin-client/api_doc.mustache index d288470ca8..5563727012 100644 --- a/src/main/resources/v2/kotlin-client/api_doc.mustache +++ b/src/main/resources/v2/kotlin-client/api_doc.mustache @@ -42,10 +42,10 @@ try { ``` ### Parameters -{{^allParams}}This endpoint does not need any parameter.{{/allParams}}{{#allParams}}{{#-last}} +{{^allParams}}This endpoint does not need any parameter.{{/allParams}}{{#allParams}}{{#@last}} Name | Type | Description | Notes -------------- | ------------- | ------------- | -------------{{/-last}}{{/allParams}} -{{#allParams}} **{{paramName}}** | {{#isPrimitiveType}}**{{dataType}}**{{/isPrimitiveType}}{{^isPrimitiveType}}{{#isFile}}**{{dataType}}**{{/isFile}}{{^isFile}}{{#generateModelDocs}}[**{{dataType}}**]({{baseType}}.md){{/generateModelDocs}}{{^generateModelDocs}}**{{dataType}}**{{/generateModelDocs}}{{/isFile}}{{/isPrimitiveType}}| {{description}} |{{^required}} [optional]{{/required}}{{#defaultValue}} [default to {{defaultValue}}]{{/defaultValue}}{{#allowableValues}} [enum: {{#values}}{{{.}}}{{^-last}}, {{/-last}}{{/values}}]{{/allowableValues}} +------------- | ------------- | ------------- | -------------{{/@last}}{{/allParams}} +{{#allParams}} **{{paramName}}** | {{#isPrimitiveType}}**{{dataType}}**{{/isPrimitiveType}}{{^isPrimitiveType}}{{#isFile}}**{{dataType}}**{{/isFile}}{{^isFile}}{{#generateModelDocs}}[**{{dataType}}**]({{baseType}}.md){{/generateModelDocs}}{{^generateModelDocs}}**{{dataType}}**{{/generateModelDocs}}{{/isFile}}{{/isPrimitiveType}}| {{description}} |{{^required}} [optional]{{/required}}{{#defaultValue}} [default to {{defaultValue}}]{{/defaultValue}}{{#allowableValues}} [enum: {{#values}}{{{.}}}{{^@last}}, {{/@last}}{{/values}}]{{/allowableValues}} {{/allParams}} ### Return type @@ -54,7 +54,7 @@ Name | Type | Description | Notes ### Authorization -{{^authMethods}}No authorization required{{/authMethods}}{{#authMethods}}[{{name}}](../README.md#{{name}}){{^-last}}, {{/-last}}{{/authMethods}} +{{^authMethods}}No authorization required{{/authMethods}}{{#authMethods}}[{{name}}](../README.md#{{name}}){{^@last}}, {{/@last}}{{/authMethods}} ### HTTP request headers diff --git a/src/main/resources/v2/kotlin-client/class_doc.mustache b/src/main/resources/v2/kotlin-client/class_doc.mustache index 45e1994273..e44b2c7269 100644 --- a/src/main/resources/v2/kotlin-client/class_doc.mustache +++ b/src/main/resources/v2/kotlin-client/class_doc.mustache @@ -11,5 +11,5 @@ Name | Type | Description | Notes ## Enum: {{baseName}} Name | Value ---- | -----{{#allowableValues}} -{{name}} | {{#values}}{{.}}{{^-last}}, {{/-last}}{{/values}}{{/allowableValues}} +{{name}} | {{#values}}{{.}}{{^@last}}, {{/@last}}{{/values}}{{/allowableValues}} {{/isEnum}}{{/vars}} diff --git a/src/main/resources/v2/kotlin-client/data_class.mustache b/src/main/resources/v2/kotlin-client/data_class.mustache index e64785e01b..d7ba7bf4c1 100644 --- a/src/main/resources/v2/kotlin-client/data_class.mustache +++ b/src/main/resources/v2/kotlin-client/data_class.mustache @@ -6,19 +6,19 @@ */ data class {{classname}} ( {{#requiredVars}} -{{>data_class_req_var}}{{^-last}}, -{{/-last}}{{/requiredVars}}{{#hasRequired}}{{#hasOptional}}, -{{/hasOptional}}{{/hasRequired}}{{#optionalVars}}{{>data_class_opt_var}}{{^-last}}, -{{/-last}}{{/optionalVars}} +{{>data_class_req_var}}{{^@last}}, +{{/@last}}{{/requiredVars}}{{#hasRequired}}{{#hasOptional}}, +{{/hasOptional}}{{/hasRequired}}{{#optionalVars}}{{>data_class_opt_var}}{{^@last}}, +{{/@last}}{{/optionalVars}} ) { {{#hasEnums}}{{#vars}}{{#isEnum}} /** * {{{description}}} - * Values: {{#allowableValues}}{{#enumVars}}{{&name}}{{^-last}},{{/-last}}{{/enumVars}}{{/allowableValues}} + * Values: {{#allowableValues}}{{#enumVars}}{{&name}}{{^@last}},{{/@last}}{{/enumVars}}{{/allowableValues}} */ enum class {{nameInCamelCase}}(val value: {{datatype}}){ {{#allowableValues}}{{#enumVars}} - {{&name}}({{{value}}}){{^-last}},{{/-last}}{{#-last}};{{/-last}} + {{&name}}({{{value}}}){{^@last}},{{/@last}}{{#@last}};{{/@last}} {{/enumVars}}{{/allowableValues}} } {{/isEnum}}{{/vars}}{{/hasEnums}} diff --git a/src/main/resources/v2/kotlin-client/enum_class.mustache b/src/main/resources/v2/kotlin-client/enum_class.mustache index 791398b978..61324894fd 100644 --- a/src/main/resources/v2/kotlin-client/enum_class.mustache +++ b/src/main/resources/v2/kotlin-client/enum_class.mustache @@ -1,9 +1,9 @@ /** * {{{description}}} -* Values: {{#allowableValues}}{{#enumVars}}{{&name}}{{^-last}},{{/-last}}{{/enumVars}}{{/allowableValues}} +* Values: {{#allowableValues}}{{#enumVars}}{{&name}}{{^@last}},{{/@last}}{{/enumVars}}{{/allowableValues}} */ enum class {{classname}}(val value: {{dataType}}){ {{#allowableValues}}{{#enumVars}} - {{&name}}({{{value}}}){{^-last}},{{/-last}}{{#-last}};{{/-last}} + {{&name}}({{{value}}}){{^@last}},{{/@last}}{{#@last}};{{/@last}} {{/enumVars}}{{/allowableValues}} } diff --git a/src/main/resources/v2/kotlin-server/api_doc.mustache b/src/main/resources/v2/kotlin-server/api_doc.mustache index d288470ca8..5563727012 100644 --- a/src/main/resources/v2/kotlin-server/api_doc.mustache +++ b/src/main/resources/v2/kotlin-server/api_doc.mustache @@ -42,10 +42,10 @@ try { ``` ### Parameters -{{^allParams}}This endpoint does not need any parameter.{{/allParams}}{{#allParams}}{{#-last}} +{{^allParams}}This endpoint does not need any parameter.{{/allParams}}{{#allParams}}{{#@last}} Name | Type | Description | Notes -------------- | ------------- | ------------- | -------------{{/-last}}{{/allParams}} -{{#allParams}} **{{paramName}}** | {{#isPrimitiveType}}**{{dataType}}**{{/isPrimitiveType}}{{^isPrimitiveType}}{{#isFile}}**{{dataType}}**{{/isFile}}{{^isFile}}{{#generateModelDocs}}[**{{dataType}}**]({{baseType}}.md){{/generateModelDocs}}{{^generateModelDocs}}**{{dataType}}**{{/generateModelDocs}}{{/isFile}}{{/isPrimitiveType}}| {{description}} |{{^required}} [optional]{{/required}}{{#defaultValue}} [default to {{defaultValue}}]{{/defaultValue}}{{#allowableValues}} [enum: {{#values}}{{{.}}}{{^-last}}, {{/-last}}{{/values}}]{{/allowableValues}} +------------- | ------------- | ------------- | -------------{{/@last}}{{/allParams}} +{{#allParams}} **{{paramName}}** | {{#isPrimitiveType}}**{{dataType}}**{{/isPrimitiveType}}{{^isPrimitiveType}}{{#isFile}}**{{dataType}}**{{/isFile}}{{^isFile}}{{#generateModelDocs}}[**{{dataType}}**]({{baseType}}.md){{/generateModelDocs}}{{^generateModelDocs}}**{{dataType}}**{{/generateModelDocs}}{{/isFile}}{{/isPrimitiveType}}| {{description}} |{{^required}} [optional]{{/required}}{{#defaultValue}} [default to {{defaultValue}}]{{/defaultValue}}{{#allowableValues}} [enum: {{#values}}{{{.}}}{{^@last}}, {{/@last}}{{/values}}]{{/allowableValues}} {{/allParams}} ### Return type @@ -54,7 +54,7 @@ Name | Type | Description | Notes ### Authorization -{{^authMethods}}No authorization required{{/authMethods}}{{#authMethods}}[{{name}}](../README.md#{{name}}){{^-last}}, {{/-last}}{{/authMethods}} +{{^authMethods}}No authorization required{{/authMethods}}{{#authMethods}}[{{name}}](../README.md#{{name}}){{^@last}}, {{/@last}}{{/authMethods}} ### HTTP request headers diff --git a/src/main/resources/v2/kotlin-server/class_doc.mustache b/src/main/resources/v2/kotlin-server/class_doc.mustache index 45e1994273..e44b2c7269 100644 --- a/src/main/resources/v2/kotlin-server/class_doc.mustache +++ b/src/main/resources/v2/kotlin-server/class_doc.mustache @@ -11,5 +11,5 @@ Name | Type | Description | Notes ## Enum: {{baseName}} Name | Value ---- | -----{{#allowableValues}} -{{name}} | {{#values}}{{.}}{{^-last}}, {{/-last}}{{/values}}{{/allowableValues}} +{{name}} | {{#values}}{{.}}{{^@last}}, {{/@last}}{{/values}}{{/allowableValues}} {{/isEnum}}{{/vars}} diff --git a/src/main/resources/v2/kotlin-server/data_class.mustache b/src/main/resources/v2/kotlin-server/data_class.mustache index 1237ec1f43..339e637555 100644 --- a/src/main/resources/v2/kotlin-server/data_class.mustache +++ b/src/main/resources/v2/kotlin-server/data_class.mustache @@ -6,19 +6,19 @@ */ data class {{classname}} ( {{#requiredVars}} -{{>data_class_req_var}}{{^-last}}, -{{/-last}}{{/requiredVars}}{{#hasRequired}}{{#hasOptional}}, -{{/hasOptional}}{{/hasRequired}}{{#optionalVars}}{{>data_class_opt_var}}{{^-last}}, -{{/-last}}{{/optionalVars}} +{{>data_class_req_var}}{{^@last}}, +{{/@last}}{{/requiredVars}}{{#hasRequired}}{{#hasOptional}}, +{{/hasOptional}}{{/hasRequired}}{{#optionalVars}}{{>data_class_opt_var}}{{^@last}}, +{{/@last}}{{/optionalVars}} ) { {{#hasEnums}}{{#vars}}{{#isEnum}} /** * {{{description}}} - * Values: {{#allowableValues}}{{#enumVars}}{{&name}}{{^-last}},{{/-last}}{{/enumVars}}{{/allowableValues}} + * Values: {{#allowableValues}}{{#enumVars}}{{&name}}{{^@last}},{{/@last}}{{/enumVars}}{{/allowableValues}} */ enum class {{nameInCamelCase}}(val value: {{dataType}}){ {{#allowableValues}}{{#enumVars}} - {{&name}}({{{value}}}){{^-last}},{{/-last}}{{#-last}};{{/-last}} + {{&name}}({{{value}}}){{^@last}},{{/@last}}{{#@last}};{{/@last}} {{/enumVars}}{{/allowableValues}} } {{/isEnum}}{{/vars}}{{/hasEnums}} diff --git a/src/main/resources/v2/kotlin-server/enum_class.mustache b/src/main/resources/v2/kotlin-server/enum_class.mustache index 791398b978..61324894fd 100644 --- a/src/main/resources/v2/kotlin-server/enum_class.mustache +++ b/src/main/resources/v2/kotlin-server/enum_class.mustache @@ -1,9 +1,9 @@ /** * {{{description}}} -* Values: {{#allowableValues}}{{#enumVars}}{{&name}}{{^-last}},{{/-last}}{{/enumVars}}{{/allowableValues}} +* Values: {{#allowableValues}}{{#enumVars}}{{&name}}{{^@last}},{{/@last}}{{/enumVars}}{{/allowableValues}} */ enum class {{classname}}(val value: {{dataType}}){ {{#allowableValues}}{{#enumVars}} - {{&name}}({{{value}}}){{^-last}},{{/-last}}{{#-last}};{{/-last}} + {{&name}}({{{value}}}){{^@last}},{{/@last}}{{#@last}};{{/@last}} {{/enumVars}}{{/allowableValues}} } From d482bd0770870af4a04868920a3fe12fa45dc3ed Mon Sep 17 00:00:00 2001 From: tcslater Date: Wed, 21 Feb 2018 12:13:22 +1100 Subject: [PATCH 03/34] Use v2 template path --- .../kotlin/AbstractKotlinCodegen.java | 56 +++++++++++-------- .../languages/kotlin/KotlinClientCodegen.java | 13 ++++- .../languages/kotlin/KotlinServerCodegen.java | 9 +++ 3 files changed, 52 insertions(+), 26 deletions(-) diff --git a/src/main/java/io/swagger/codegen/languages/kotlin/AbstractKotlinCodegen.java b/src/main/java/io/swagger/codegen/languages/kotlin/AbstractKotlinCodegen.java index 3f2f70b94e..8310d5a766 100644 --- a/src/main/java/io/swagger/codegen/languages/kotlin/AbstractKotlinCodegen.java +++ b/src/main/java/io/swagger/codegen/languages/kotlin/AbstractKotlinCodegen.java @@ -243,30 +243,7 @@ public void setEnumPropertyNaming(final String enumPropertyNamingType) { } throw new RuntimeException(sb.toString()); } - } - - // /** - // * returns the swagger type for the property - // * - // * @param p Swagger property object - // * @return string presentation of the type - // **/ - // @Override - // public String getSwaggerType(Schema p) { - // String swaggerType = super.getSwaggerType(p); - // String type; - // // This maps, for example, long -> kotlin.Long based on hashes in this type's constructor - // if (typeMapping.containsKey(swaggerType)) { - // type = typeMapping.get(swaggerType); - // if (languageSpecificPrimitives.contains(type)) { - // return toModelName(type); - // } - // } else { - // type = swaggerType; - // } - // return toModelName(type); - // } - + } /** * Output the type declaration of the property @@ -292,6 +269,37 @@ public String getTypeDeclaration(Schema propertySchema) { return super.getTypeDeclaration(propertySchema); } + @Override + public String getAlias(String name) { + if (typeAliases != null && typeAliases.containsKey(name)) { + return typeAliases.get(name); + } + return name; + } + + @Override + public String getSchemaType(Schema schema) { + String schemaType = super.getSchemaType(schema); + + schemaType = getAlias(schemaType); + + // don't apply renaming on types from the typeMapping + if (typeMapping.containsKey(schemaType)) { + return toModelName(typeMapping.get(schemaType)); + } + + if (null == schemaType) { + if (schema.getName() != null) { + LOGGER.warn("No Type defined for Property " + schema.getName()); + } else { + // LOGGER.error(schema.toString()); + // LOGGER.error("No Type defined.", new Exception()); + return "XXXXXX"; + } + } + return toModelName(schemaType); + } + @Override public String modelDocFileFolder() { return (outputFolder + "/" + modelDocPath).replace('/', File.separatorChar); diff --git a/src/main/java/io/swagger/codegen/languages/kotlin/KotlinClientCodegen.java b/src/main/java/io/swagger/codegen/languages/kotlin/KotlinClientCodegen.java index ef2c20983a..27a68e53c6 100644 --- a/src/main/java/io/swagger/codegen/languages/kotlin/KotlinClientCodegen.java +++ b/src/main/java/io/swagger/codegen/languages/kotlin/KotlinClientCodegen.java @@ -11,6 +11,8 @@ import java.util.HashMap; import java.util.Map; +import org.apache.commons.lang3.StringUtils; + public class KotlinClientCodegen extends AbstractKotlinCodegen { public static final String DATE_LIBRARY = "dateLibrary"; @@ -76,13 +78,20 @@ public void setDateLibrary(String library) { @Override public void processOpts() { - super.processOpts(); + super.processOpts(); + + String templateVersion = getTemplateVersion(); + if (StringUtils.isNotBlank(templateVersion)) { + embeddedTemplateDir = templateDir = String.format("%s/kotlin-client", templateVersion); + } else { + embeddedTemplateDir = templateDir = String.format("%s/kotlin-client", DEFAULT_TEMPLATE_VERSION); + } if (additionalProperties.containsKey(DATE_LIBRARY)) { setDateLibrary(additionalProperties.get(DATE_LIBRARY).toString()); } - if (DateLibrary.THREETENBP.value.equals(dateLibrary)) { + if (DateLibrary.THREETENBP.value.equals(dateLibrary)) { additionalProperties.put(DateLibrary.THREETENBP.value, true); typeMapping.put("date", "LocalDate"); typeMapping.put("DateTime", "LocalDateTime"); diff --git a/src/main/java/io/swagger/codegen/languages/kotlin/KotlinServerCodegen.java b/src/main/java/io/swagger/codegen/languages/kotlin/KotlinServerCodegen.java index b0781d8369..4ea3b645dd 100644 --- a/src/main/java/io/swagger/codegen/languages/kotlin/KotlinServerCodegen.java +++ b/src/main/java/io/swagger/codegen/languages/kotlin/KotlinServerCodegen.java @@ -15,6 +15,8 @@ import java.util.List; import java.util.Map; +import org.apache.commons.lang3.StringUtils; + public class KotlinServerCodegen extends AbstractKotlinCodegen { public static final String DEFAULT_LIBRARY = Constants.KTOR; @@ -123,6 +125,13 @@ public CodegenType getTag() { public void processOpts() { super.processOpts(); + String templateVersion = getTemplateVersion(); + if (StringUtils.isNotBlank(templateVersion)) { + embeddedTemplateDir = templateDir = String.format("%s/kotlin-server", templateVersion); + } else { + embeddedTemplateDir = templateDir = String.format("%s/kotlin-server", DEFAULT_TEMPLATE_VERSION); + } + if (additionalProperties.containsKey(CodegenConstants.LIBRARY)) { this.setLibrary((String) additionalProperties.get(CodegenConstants.LIBRARY)); } From 9a6b4df84a168b1fd52f8c87e6ab577ae7cb5367 Mon Sep 17 00:00:00 2001 From: tcslater Date: Wed, 21 Feb 2018 12:13:49 +1100 Subject: [PATCH 04/34] Updating tests [WIP] --- .../kotlin/KotlinClientCodegenModelTest.java | 210 +++++++++--------- 1 file changed, 109 insertions(+), 101 deletions(-) diff --git a/src/test/java/io/swagger/codegen/languages/kotlin/KotlinClientCodegenModelTest.java b/src/test/java/io/swagger/codegen/languages/kotlin/KotlinClientCodegenModelTest.java index 4e1c9864ba..e16bd3b9b6 100644 --- a/src/test/java/io/swagger/codegen/languages/kotlin/KotlinClientCodegenModelTest.java +++ b/src/test/java/io/swagger/codegen/languages/kotlin/KotlinClientCodegenModelTest.java @@ -1,7 +1,10 @@ package io.swagger.codegen.languages.kotlin; +import com.google.common.collect.Sets; + import io.swagger.codegen.*; import io.swagger.codegen.languages.kotlin.KotlinClientCodegen; +import io.swagger.codegen.languages.DefaultCodegenConfig; import io.swagger.models.*; import io.swagger.models.properties.*; @@ -21,16 +24,20 @@ import org.testng.annotations.DataProvider; import org.testng.annotations.Test; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + import static io.swagger.codegen.languages.helpers.ExtensionHelper.getBooleanValue; @SuppressWarnings("static-method") public class KotlinClientCodegenModelTest { - private Schema getArrayTestSchema() { - // Schema propertySchema = new ArraySchema() - // .items(new Schema().$ref("#/components/schemas/Child")) - // .description("an array property"); - // propertySchema.addExtension("x-item-name", "child"); + protected static final Logger LOGGER = LoggerFactory.getLogger(KotlinClientCodegenModelTest.class); + + private Schema getArrayTestSchema() { return new ArraySchema() .items(new StringSchema()) .description("a sample model") @@ -38,7 +45,6 @@ private Schema getArrayTestSchema() { } private Schema getSimpleSchema() { - return new Schema() .description("a sample model") .addProperties("id", new IntegerSchema() @@ -50,24 +56,23 @@ private Schema getSimpleSchema() { .addRequiredItem("name"); } - private Schema getMapSchema() { - return new Schema() - .description("a sample model") - .additionalProperties(new Schema().$ref("#/components/schemas/Children")); - } + // private Schema getMapSchema() { + // return new Schema() + // .description("a sample model") + // .additionalProperties(new Schema().$ref("#/components/schemas/Children")); + // } - private Schema getComplexSchema() { - return new Schema() - .description("a sample model") - .additionalProperties(new Schema().$ref("#/definitions/Child")); - } + // private Schema getComplexSchema() { + // return new Schema() + // .description("a sample model") + // .additionalProperties(new Schema().$ref("#/definitions/Child")); + // } @Test(description = "convert a simple model") public void simpleModelTest() { // final Model model = getSimpleModel(); final Schema schema = getSimpleSchema(); - final CodegenConfig codegen = new KotlinClientCodegen(); - + final KotlinClientCodegen codegen = new KotlinClientCodegen(); final CodegenModel cm = codegen.fromModel("sample", schema); Assert.assertEquals(cm.name, "sample"); @@ -103,7 +108,7 @@ public void simpleModelTest() { Assert.assertEquals(property3.name, "createdAt"); Assert.assertEquals(property3.defaultValue, "null"); Assert.assertEquals(property3.baseType, "java.time.LocalDateTime"); - Assert.assertTrue(getBooleanValue(property3, CodegenConstants.HAS_MORE_EXT_NAME)); + Assert.assertFalse(getBooleanValue(property3, CodegenConstants.HAS_MORE_EXT_NAME)); Assert.assertFalse(property3.required); Assert.assertTrue(getBooleanValue(property3, CodegenConstants.IS_NOT_CONTAINER_EXT_NAME)); } @@ -123,7 +128,7 @@ public void selectDateLibraryAsThreetenbp() { Assert.assertEquals(property3.name, "createdAt"); Assert.assertEquals(property3.defaultValue, "null"); Assert.assertEquals(property3.baseType, "org.threeten.bp.LocalDateTime"); - Assert.assertTrue(getBooleanValue(property3, CodegenConstants.HAS_MORE_EXT_NAME)); + Assert.assertFalse(getBooleanValue(property3, CodegenConstants.HAS_MORE_EXT_NAME)); Assert.assertFalse(property3.required); Assert.assertTrue(getBooleanValue(property3, CodegenConstants.IS_NOT_CONTAINER_EXT_NAME)); } @@ -143,7 +148,7 @@ public void selectDateLibraryAsString() { Assert.assertEquals(property3.name, "createdAt"); Assert.assertEquals(property3.defaultValue, "null"); Assert.assertEquals(property3.baseType, "kotlin.String"); - Assert.assertTrue(getBooleanValue(property3, CodegenConstants.HAS_MORE_EXT_NAME)); + Assert.assertFalse(getBooleanValue(property3, CodegenConstants.HAS_MORE_EXT_NAME)); Assert.assertFalse(property3.required); Assert.assertTrue(getBooleanValue(property3, CodegenConstants.IS_NOT_CONTAINER_EXT_NAME)); } @@ -163,7 +168,7 @@ public void selectDateLibraryAsJava8() { Assert.assertEquals(property3.name, "createdAt"); Assert.assertEquals(property3.defaultValue, "null"); Assert.assertEquals(property3.baseType, "java.time.LocalDateTime"); - Assert.assertTrue(getBooleanValue(property3, CodegenConstants.HAS_MORE_EXT_NAME)); + Assert.assertFalse(getBooleanValue(property3, CodegenConstants.HAS_MORE_EXT_NAME)); Assert.assertFalse(property3.required); Assert.assertTrue(getBooleanValue(property3, CodegenConstants.IS_NOT_CONTAINER_EXT_NAME)); } @@ -175,12 +180,15 @@ public void arrayPropertyTest() { final CodegenConfig codegen = new KotlinClientCodegen(); final CodegenModel generated = codegen.fromModel("sample", schema); - System.err.println(generated.toString()); - Assert.assertEquals(generated.name, "sample"); Assert.assertEquals(generated.classname, "Sample"); Assert.assertEquals(generated.description, "a sample model"); Assert.assertEquals(generated.vars.size(), 0); + + Assert.assertEquals(generated.parent, "kotlin.Array"); + Assert.assertEquals(generated.imports.size(), 1); + + LOGGER.error(generated.imports.toArray()[0].toString()); // final CodegenProperty property = generated.vars.get(0); // Assert.assertEquals(property.baseName, "examples"); @@ -195,82 +203,82 @@ public void arrayPropertyTest() { // Assert.assertTrue(getBooleanValue(property, CodegenConstants.IS_CONTAINER_EXT_NAME)); } - @Test(description = "convert a model with a map property") - public void mapPropertyTest() { - final Schema schema = getMapSchema(); - final CodegenConfig codegen = new KotlinClientCodegen(); - final CodegenModel cm = codegen.fromModel("sample", schema); - - Assert.assertEquals(cm.name, "sample"); - Assert.assertEquals(cm.classname, "Sample"); - Assert.assertEquals(cm.description, "a sample model"); - Assert.assertEquals(cm.vars.size(), 1); - - final CodegenProperty property1 = cm.vars.get(0); - Assert.assertEquals(property1.baseName, "mapping"); - Assert.assertEquals(property1.datatype, "kotlin.collections.Map"); - Assert.assertEquals(property1.name, "mapping"); - Assert.assertEquals(property1.baseType, "kotlin.collections.Map"); - Assert.assertEquals(property1.containerType, "map"); - Assert.assertFalse(property1.required); - Assert.assertTrue(getBooleanValue(property1, CodegenConstants.IS_CONTAINER_EXT_NAME)); - Assert.assertTrue(getBooleanValue(property1, CodegenConstants.IS_PRIMITIVE_TYPE_EXT_NAME)); - } - - @Test(description = "convert a model with complex property") - public void complexPropertyTest() { - final Schema schema = getComplexSchema(); - final CodegenConfig codegen = new KotlinClientCodegen(); - final CodegenModel cm = codegen.fromModel("sample", schema); - - Assert.assertEquals(cm.name, "sample"); - Assert.assertEquals(cm.classname, "Sample"); - Assert.assertEquals(cm.description, "a sample model"); - Assert.assertEquals(cm.vars.size(), 1); - - final CodegenProperty property1 = cm.vars.get(0); - Assert.assertEquals(property1.baseName, "child"); - Assert.assertEquals(property1.datatype, "Child"); - Assert.assertEquals(property1.name, "child"); - Assert.assertEquals(property1.baseType, "Child"); - Assert.assertFalse(property1.required); - Assert.assertTrue(getBooleanValue(property1, CodegenConstants.IS_NOT_CONTAINER_EXT_NAME)); - } - - @DataProvider(name = "modelNames") - public static Object[][] modelNames() { - return new Object[][]{ - {"TestNs.TestClass", new ModelNameTest("TestNs.TestClass", "TestNsTestClass")}, - {"$", new ModelNameTest("$", "Dollar")}, - {"for", new ModelNameTest("`for`", "`for`")}, - {"One"); + // // Assert.assertEquals(property1.name, "mapping"); + // // Assert.assertEquals(property1.baseType, "kotlin.collections.Map"); + // // Assert.assertEquals(property1.containerType, "map"); + // // Assert.assertFalse(property1.required); + // // Assert.assertTrue(getBooleanValue(property1, CodegenConstants.IS_CONTAINER_EXT_NAME)); + // // Assert.assertTrue(getBooleanValue(property1, CodegenConstants.IS_PRIMITIVE_TYPE_EXT_NAME)); + // } + + // @Test(description = "convert a model with complex property") + // public void complexPropertyTest() { + // final Schema schema = getComplexSchema(); + // final CodegenConfig codegen = new KotlinClientCodegen(); + // final CodegenModel cm = codegen.fromModel("sample", schema); + + // Assert.assertEquals(cm.name, "sample"); + // Assert.assertEquals(cm.classname, "Sample"); + // Assert.assertEquals(cm.description, "a sample model"); + // Assert.assertEquals(cm.vars.size(), 0); + + // // final CodegenProperty property1 = cm.vars.get(0); + // // Assert.assertEquals(property1.baseName, "child"); + // // Assert.assertEquals(property1.datatype, "Child"); + // // Assert.assertEquals(property1.name, "child"); + // // Assert.assertEquals(property1.baseType, "Child"); + // // Assert.assertFalse(property1.required); + // // Assert.assertTrue(getBooleanValue(property1, CodegenConstants.IS_NOT_CONTAINER_EXT_NAME)); + // } + + // @DataProvider(name = "modelNames") + // public static Object[][] modelNames() { + // return new Object[][]{ + // {"TestNs.TestClass", new ModelNameTest("TestNs.TestClass", "TestNsTestClass")}, + // {"$", new ModelNameTest("$", "Dollar")}, + // {"for", new ModelNameTest("`for`", "`for`")}, + // {"One Date: Wed, 21 Feb 2018 13:32:50 +1100 Subject: [PATCH 05/34] Updated references to is*, isNot*, has*, hasNot* to use new is/has helpers --- .gitignore | 5 ++++- .../resources/v2/kotlin-client/README.mustache | 14 +++++++------- src/main/resources/v2/kotlin-client/api.mustache | 8 ++++---- .../resources/v2/kotlin-client/api_doc.mustache | 10 +++++----- .../resources/v2/kotlin-client/class_doc.mustache | 6 +++--- .../resources/v2/kotlin-client/data_class.mustache | 4 ++-- .../v2/kotlin-client/data_class_opt_var.mustache | 2 +- .../v2/kotlin-client/data_class_req_var.mustache | 2 +- src/main/resources/v2/kotlin-client/model.mustache | 2 +- .../resources/v2/kotlin-client/model_doc.mustache | 2 +- .../resources/v2/kotlin-server/README.mustache | 14 +++++++------- .../resources/v2/kotlin-server/api_doc.mustache | 10 +++++----- .../resources/v2/kotlin-server/class_doc.mustache | 6 +++--- .../resources/v2/kotlin-server/data_class.mustache | 4 ++-- .../v2/kotlin-server/data_class_opt_var.mustache | 2 +- .../v2/kotlin-server/data_class_req_var.mustache | 2 +- .../libraries/ktor/Configuration.kt.mustache | 4 ++-- .../kotlin-server/libraries/ktor/Paths.kt.mustache | 2 +- src/main/resources/v2/kotlin-server/model.mustache | 2 +- .../resources/v2/kotlin-server/model_doc.mustache | 2 +- 20 files changed, 53 insertions(+), 50 deletions(-) diff --git a/.gitignore b/.gitignore index 2419a1981a..c9d68cad87 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,9 @@ # BlueJ files *.ctxt +# Visual Studio Code +.project + # Mobile Tools for Java (J2ME) .mtj.tmp/ @@ -22,4 +25,4 @@ hs_err_pid* .idea -target/ +target/ \ No newline at end of file diff --git a/src/main/resources/v2/kotlin-client/README.mustache b/src/main/resources/v2/kotlin-client/README.mustache index 7a10bab6c1..90b6f3e2db 100644 --- a/src/main/resources/v2/kotlin-client/README.mustache +++ b/src/main/resources/v2/kotlin-client/README.mustache @@ -68,18 +68,18 @@ Authentication schemes defined for the API: ### {{name}} -{{#isApiKey}}- **Type**: API key +{{#is this 'apiKey'}}- **Type**: API key - **API key parameter name**: {{keyParamName}} -- **Location**: {{#isKeyInQuery}}URL query string{{/isKeyInQuery}}{{#isKeyInHeader}}HTTP header{{/isKeyInHeader}} -{{/isApiKey}} -{{#isBasic}}- **Type**: HTTP basic authentication -{{/isBasic}} -{{#isOAuth}}- **Type**: OAuth +- **Location**: {{#is this 'keyInQuery'}}URL query string{{/is}}{{#is this 'keyInHeader'}}HTTP header{{/is}} +{{/is}} +{{#is this 'basic'}}- **Type**: HTTP basic authentication +{{/is}} +{{#is this 'oauth'}}- **Type**: OAuth - **Flow**: {{flow}} - **Authorization URL**: {{authorizationUrl}} - **Scopes**: {{^scopes}}N/A{{/scopes}} {{#scopes}} - {{scope}}: {{description}} {{/scopes}} -{{/isOAuth}} +{{/is}} {{/authMethods}} \ No newline at end of file diff --git a/src/main/resources/v2/kotlin-client/api.mustache b/src/main/resources/v2/kotlin-client/api.mustache index f479302b7d..4c2d0c3a67 100644 --- a/src/main/resources/v2/kotlin-client/api.mustache +++ b/src/main/resources/v2/kotlin-client/api.mustache @@ -20,10 +20,10 @@ class {{classname}}(basePath: kotlin.String = "{{{basePath}}}") : ApiClient(base {{/allParams}}* @return {{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}} */{{#returnType}} @Suppress("UNCHECKED_CAST"){{/returnType}} - fun {{operationId}}({{#allParams}}{{paramName}}: {{{dataType}}}{{#hasMore}}, {{/hasMore}}{{/allParams}}) : {{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}Unit{{/returnType}} { - val localVariableBody: kotlin.Any? = {{#hasBodyParam}}{{#bodyParams}}{{paramName}}{{/bodyParams}}{{/hasBodyParam}}{{^hasBodyParam}}{{^hasFormParams}}null{{/hasFormParams}}{{#hasFormParams}}mapOf({{#formParams}}"{{{baseName}}}" to "${{paramName}}"{{#hasMore}}, {{/hasMore}}{{/formParams}}){{/hasFormParams}}{{/hasBodyParam}} - 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}} - val localVariableHeaders: kotlin.collections.Map = mapOf({{#hasFormParams}}"Content-Type" to "multipart/form-data"{{/hasFormParams}}{{^hasHeaderParams}}){{/hasHeaderParams}}{{#hasHeaderParams}}{{#hasFormParams}}, {{/hasFormParams}}{{#headerParams}}"{{baseName}}" to {{#isContainer}}{{paramName}}.joinToString(separator = collectionDelimiter("{{collectionFormat}}"){{/isContainer}}{{^isContainer}}{{paramName}}{{/isContainer}}{{#hasMore}}, {{/hasMore}}{{/headerParams}}){{/hasHeaderParams}} + fun {{operationId}}({{#allParams}}{{paramName}}: {{{dataType}}}{{#has this 'more'}}, {{/has}}{{/allParams}}) : {{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}Unit{{/returnType}} { + val localVariableBody: kotlin.Any? = {{#hasBodyParam}}{{#bodyParams}}{{paramName}}{{/bodyParams}}{{/hasBodyParam}}{{^hasBodyParam}}{{^hasFormParams}}null{{/hasFormParams}}{{#hasFormParams}}mapOf({{#formParams}}"{{{baseName}}}" to "${{paramName}}"{{#has this 'more'}}, {{/has}}{{/formParams}}){{/hasFormParams}}{{/hasBodyParam}} + val localVariableQuery: MultiValueMap = {{^hasQueryParams}}mapOf(){{/hasQueryParams}}{{#hasQueryParams}}mapOf({{#queryParams}}"{{baseName}}" to {{#is this 'container'}}toMultiValue({{paramName}}.toList(), "{{collectionFormat}}"){{/is}}{{#isNot this 'container'}}listOf("${{paramName}}"){{/isNot}}{{#has this 'more'}}, {{/has}}{{/queryParams}}){{/hasQueryParams}} + val localVariableHeaders: kotlin.collections.Map = mapOf({{#hasFormParams}}"Content-Type" to "multipart/form-data"{{/hasFormParams}}{{^hasHeaderParams}}){{/hasHeaderParams}}{{#hasHeaderParams}}{{#hasFormParams}}, {{/hasFormParams}}{{#headerParams}}"{{baseName}}" to {{#is this 'container'}}{{paramName}}.joinToString(separator = collectionDelimiter("{{collectionFormat}}"){{/is}}{{#isNot this 'container'}}{{paramName}}{{/isNot}}{{#has this 'more'}}, {{/has}}{{/headerParams}}){{/hasHeaderParams}} val localVariableConfig = RequestConfig( RequestMethod.{{httpMethod}}, "{{path}}"{{#pathParams}}.replace("{"+"{{baseName}}"+"}", "${{paramName}}"){{/pathParams}}, diff --git a/src/main/resources/v2/kotlin-client/api_doc.mustache b/src/main/resources/v2/kotlin-client/api_doc.mustache index 5563727012..b8a85201c5 100644 --- a/src/main/resources/v2/kotlin-client/api_doc.mustache +++ b/src/main/resources/v2/kotlin-client/api_doc.mustache @@ -12,7 +12,7 @@ Method | HTTP request | Description {{#operation}} # **{{operationId}}** -> {{#returnType}}{{returnType}} {{/returnType}}{{operationId}}({{#allParams}}{{{paramName}}}{{#hasMore}}, {{/hasMore}}{{/allParams}}) +> {{#returnType}}{{returnType}} {{/returnType}}{{operationId}}({{#allParams}}{{{paramName}}}{{#has this 'more'}}, {{/has}}{{/allParams}}) {{summary}}{{#notes}} @@ -30,7 +30,7 @@ val apiInstance = {{{classname}}}() val {{{paramName}}} : {{{dataType}}} = {{{example}}} // {{{dataType}}} | {{{description}}} {{/allParams}} try { - {{#returnType}}val result : {{{returnType}}} = {{/returnType}}apiInstance.{{{operationId}}}({{#allParams}}{{{paramName}}}{{#hasMore}}, {{/hasMore}}{{/allParams}}){{#returnType}} + {{#returnType}}val result : {{{returnType}}} = {{/returnType}}apiInstance.{{{operationId}}}({{#allParams}}{{{paramName}}}{{#has this 'more'}}, {{/has}}{{/allParams}}){{#returnType}} println(result){{/returnType}} } catch (e: ClientException) { println("4xx response calling {{{classname}}}#{{{operationId}}}") @@ -45,7 +45,7 @@ try { {{^allParams}}This endpoint does not need any parameter.{{/allParams}}{{#allParams}}{{#@last}} Name | Type | Description | Notes ------------- | ------------- | ------------- | -------------{{/@last}}{{/allParams}} -{{#allParams}} **{{paramName}}** | {{#isPrimitiveType}}**{{dataType}}**{{/isPrimitiveType}}{{^isPrimitiveType}}{{#isFile}}**{{dataType}}**{{/isFile}}{{^isFile}}{{#generateModelDocs}}[**{{dataType}}**]({{baseType}}.md){{/generateModelDocs}}{{^generateModelDocs}}**{{dataType}}**{{/generateModelDocs}}{{/isFile}}{{/isPrimitiveType}}| {{description}} |{{^required}} [optional]{{/required}}{{#defaultValue}} [default to {{defaultValue}}]{{/defaultValue}}{{#allowableValues}} [enum: {{#values}}{{{.}}}{{^@last}}, {{/@last}}{{/values}}]{{/allowableValues}} +{{#allParams}} **{{paramName}}** | {{#is this 'primitiveType'}}}**{{dataType}}**{{/is}}}{{#isNot this 'primitiveType'}}{{#is this 'file'}}**{{dataType}}**{{/is}}{{#isNot this 'file'}}{{#generateModelDocs}}[**{{dataType}}**]({{baseType}}.md){{/generateModelDocs}}{{^generateModelDocs}}**{{dataType}}**{{/generateModelDocs}}{{/isNot}}{{/isNot}}}| {{description}} |{{^required}} [optional]{{/required}}{{#defaultValue}} [default to {{defaultValue}}]{{/defaultValue}}{{#allowableValues}} [enum: {{#values}}{{{.}}}{{^@last}}, {{/@last}}{{/values}}]{{/allowableValues}} {{/allParams}} ### Return type @@ -58,8 +58,8 @@ Name | Type | Description | Notes ### HTTP request headers - - **Content-Type**: {{#consumes}}{{{mediaType}}}{{#hasMore}}, {{/hasMore}}{{/consumes}}{{^consumes}}Not defined{{/consumes}} - - **Accept**: {{#produces}}{{{mediaType}}}{{#hasMore}}, {{/hasMore}}{{/produces}}{{^produces}}Not defined{{/produces}} + - **Content-Type**: {{#consumes}}{{{mediaType}}}{{#has this 'more'}}, {{/has}}{{/consumes}}{{^consumes}}Not defined{{/consumes}} + - **Accept**: {{#produces}}{{{mediaType}}}{{#has this 'more'}}, {{/has}}{{/produces}}{{^produces}}Not defined{{/produces}} {{/operation}} {{/operations}} diff --git a/src/main/resources/v2/kotlin-client/class_doc.mustache b/src/main/resources/v2/kotlin-client/class_doc.mustache index e44b2c7269..e4c65efbea 100644 --- a/src/main/resources/v2/kotlin-client/class_doc.mustache +++ b/src/main/resources/v2/kotlin-client/class_doc.mustache @@ -3,13 +3,13 @@ ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -{{#vars}}**{{name}}** | {{#isEnum}}[**inline**](#{{datatypeWithEnum}}){{/isEnum}}{{^isEnum}}{{#isPrimitiveType}}**{{datatype}}**{{/isPrimitiveType}}{{^isPrimitiveType}}[**{{datatype}}**]({{complexType}}.md){{/isPrimitiveType}}{{/isEnum}} | {{description}} | {{^required}} [optional]{{/required}}{{#readOnly}} [readonly]{{/readOnly}} +{{#vars}}**{{name}}** | {{#is this 'enum'}}[**inline**](#{{datatypeWithEnum}}){{/is}}{{#isNot this 'enum'}}{{#is this 'primitiveType'}}}**{{datatype}}**{{/is}}}{{#isNot this 'primitiveType'}}[**{{datatype}}**]({{complexType}}.md){{/is}}}{{/is}} | {{description}} | {{^required}} [optional]{{/required}}{{#readOnly}} [readonly]{{/readOnly}} {{/vars}} -{{#vars}}{{#isEnum}} +{{#vars}}{{#is this 'enum'}} {{!NOTE: see java's resources "pojo_doc.mustache" once enums are fully implemented}} ## Enum: {{baseName}} Name | Value ---- | -----{{#allowableValues}} {{name}} | {{#values}}{{.}}{{^@last}}, {{/@last}}{{/values}}{{/allowableValues}} -{{/isEnum}}{{/vars}} +{{/is}}{{/vars}} diff --git a/src/main/resources/v2/kotlin-client/data_class.mustache b/src/main/resources/v2/kotlin-client/data_class.mustache index d7ba7bf4c1..2429a754b6 100644 --- a/src/main/resources/v2/kotlin-client/data_class.mustache +++ b/src/main/resources/v2/kotlin-client/data_class.mustache @@ -11,7 +11,7 @@ data class {{classname}} ( {{/hasOptional}}{{/hasRequired}}{{#optionalVars}}{{>data_class_opt_var}}{{^@last}}, {{/@last}}{{/optionalVars}} ) { -{{#hasEnums}}{{#vars}}{{#isEnum}} +{{#hasEnums}}{{#vars}}{{#is this 'enum'}} /** * {{{description}}} * Values: {{#allowableValues}}{{#enumVars}}{{&name}}{{^@last}},{{/@last}}{{/enumVars}}{{/allowableValues}} @@ -21,5 +21,5 @@ data class {{classname}} ( {{&name}}({{{value}}}){{^@last}},{{/@last}}{{#@last}};{{/@last}} {{/enumVars}}{{/allowableValues}} } -{{/isEnum}}{{/vars}}{{/hasEnums}} +{{/is}}{{/vars}}{{/hasEnums}} } diff --git a/src/main/resources/v2/kotlin-client/data_class_opt_var.mustache b/src/main/resources/v2/kotlin-client/data_class_opt_var.mustache index a88761ea90..7b1c72aa76 100644 --- a/src/main/resources/v2/kotlin-client/data_class_opt_var.mustache +++ b/src/main/resources/v2/kotlin-client/data_class_opt_var.mustache @@ -1,4 +1,4 @@ {{#description}} /* {{{description}}} */ {{/description}} - val {{{name}}}: {{#isEnum}}{{classname}}.{{nameInCamelCase}}{{/isEnum}}{{^isEnum}}{{{datatype}}}{{/isEnum}}? = {{#defaultvalue}}{{defaultvalue}}{{/defaultvalue}}{{^defaultvalue}}null{{/defaultvalue}} \ No newline at end of file + val {{{name}}}: {{#is this 'enum'}}{{classname}}.{{nameInCamelCase}}{{/is}}{{#isNot this 'enum'}}{{{datatype}}}{{/isNot}}? = {{#defaultvalue}}{{defaultvalue}}{{/defaultvalue}}{{^defaultvalue}}null{{/defaultvalue}} \ No newline at end of file diff --git a/src/main/resources/v2/kotlin-client/data_class_req_var.mustache b/src/main/resources/v2/kotlin-client/data_class_req_var.mustache index 8a33a15a18..659ceefc24 100644 --- a/src/main/resources/v2/kotlin-client/data_class_req_var.mustache +++ b/src/main/resources/v2/kotlin-client/data_class_req_var.mustache @@ -1,4 +1,4 @@ {{#description}} /* {{{description}}} */ {{/description}} - val {{{name}}}: {{#isEnum}}{{classname}}.{{nameInCamelCase}}{{/isEnum}}{{^isEnum}}{{{datatype}}}{{/isEnum}} \ No newline at end of file + val {{{name}}}: {{#is this 'enum'}}{{classname}}.{{nameInCamelCase}}{{/is}}{{#isNot this 'enum'}}{{{datatype}}}{{/isNot}} \ No newline at end of file diff --git a/src/main/resources/v2/kotlin-client/model.mustache b/src/main/resources/v2/kotlin-client/model.mustache index bb27cf99c5..2d8ffcb343 100644 --- a/src/main/resources/v2/kotlin-client/model.mustache +++ b/src/main/resources/v2/kotlin-client/model.mustache @@ -9,6 +9,6 @@ import org.threeten.bp.LocalDateTime {{#models}} {{#model}} -{{#isEnum}}{{>enum_class}}{{/isEnum}}{{^isEnum}}{{>data_class}}{{/isEnum}} +{{#is this 'enum'}}{{>enum_class}}{{/is}}{{#isNot this 'enum'}}{{>data_class}}{{/isNot}} {{/model}} {{/models}} diff --git a/src/main/resources/v2/kotlin-client/model_doc.mustache b/src/main/resources/v2/kotlin-client/model_doc.mustache index e3b7184211..e11fac0390 100644 --- a/src/main/resources/v2/kotlin-client/model_doc.mustache +++ b/src/main/resources/v2/kotlin-client/model_doc.mustache @@ -1,3 +1,3 @@ {{#models}}{{#model}} -{{#isEnum}}{{>enum_doc}}{{/isEnum}}{{^isEnum}}{{>class_doc}}{{/isEnum}} +{{#is this 'enum'}}{{>enum_doc}}{{/is}}{{#isNot this 'enum'}}{{>class_doc}}{{/isNot}} {{/model}}{{/models}} diff --git a/src/main/resources/v2/kotlin-server/README.mustache b/src/main/resources/v2/kotlin-server/README.mustache index 7b53516d38..de584f694f 100644 --- a/src/main/resources/v2/kotlin-server/README.mustache +++ b/src/main/resources/v2/kotlin-server/README.mustache @@ -67,18 +67,18 @@ Authentication schemes defined for the API: ### {{name}} -{{#isApiKey}}- **Type**: API key +{{#is this 'apiKey'}}- **Type**: API key - **API key parameter name**: {{keyParamName}} -- **Location**: {{#isKeyInQuery}}URL query string{{/isKeyInQuery}}{{#isKeyInHeader}}HTTP header{{/isKeyInHeader}} -{{/isApiKey}} -{{#isBasic}}- **Type**: HTTP basic authentication -{{/isBasic}} -{{#isOAuth}}- **Type**: OAuth +- **Location**: {{#is this 'keyInQuery'}}URL query string{{/is}}{{#is this 'keyInHeader'}}HTTP header{{/is}} +{{/is}} +{{#is this 'basic'}}- **Type**: HTTP basic authentication +{{/is}} +{{#is this 'oauth'}}- **Type**: OAuth - **Flow**: {{flow}} - **Authorization URL**: {{authorizationUrl}} - **Scopes**: {{^scopes}}N/A{{/scopes}} {{#scopes}} - {{scope}}: {{description}} {{/scopes}} -{{/isOAuth}} +{{/is}} {{/authMethods}} \ No newline at end of file diff --git a/src/main/resources/v2/kotlin-server/api_doc.mustache b/src/main/resources/v2/kotlin-server/api_doc.mustache index 5563727012..b8a85201c5 100644 --- a/src/main/resources/v2/kotlin-server/api_doc.mustache +++ b/src/main/resources/v2/kotlin-server/api_doc.mustache @@ -12,7 +12,7 @@ Method | HTTP request | Description {{#operation}} # **{{operationId}}** -> {{#returnType}}{{returnType}} {{/returnType}}{{operationId}}({{#allParams}}{{{paramName}}}{{#hasMore}}, {{/hasMore}}{{/allParams}}) +> {{#returnType}}{{returnType}} {{/returnType}}{{operationId}}({{#allParams}}{{{paramName}}}{{#has this 'more'}}, {{/has}}{{/allParams}}) {{summary}}{{#notes}} @@ -30,7 +30,7 @@ val apiInstance = {{{classname}}}() val {{{paramName}}} : {{{dataType}}} = {{{example}}} // {{{dataType}}} | {{{description}}} {{/allParams}} try { - {{#returnType}}val result : {{{returnType}}} = {{/returnType}}apiInstance.{{{operationId}}}({{#allParams}}{{{paramName}}}{{#hasMore}}, {{/hasMore}}{{/allParams}}){{#returnType}} + {{#returnType}}val result : {{{returnType}}} = {{/returnType}}apiInstance.{{{operationId}}}({{#allParams}}{{{paramName}}}{{#has this 'more'}}, {{/has}}{{/allParams}}){{#returnType}} println(result){{/returnType}} } catch (e: ClientException) { println("4xx response calling {{{classname}}}#{{{operationId}}}") @@ -45,7 +45,7 @@ try { {{^allParams}}This endpoint does not need any parameter.{{/allParams}}{{#allParams}}{{#@last}} Name | Type | Description | Notes ------------- | ------------- | ------------- | -------------{{/@last}}{{/allParams}} -{{#allParams}} **{{paramName}}** | {{#isPrimitiveType}}**{{dataType}}**{{/isPrimitiveType}}{{^isPrimitiveType}}{{#isFile}}**{{dataType}}**{{/isFile}}{{^isFile}}{{#generateModelDocs}}[**{{dataType}}**]({{baseType}}.md){{/generateModelDocs}}{{^generateModelDocs}}**{{dataType}}**{{/generateModelDocs}}{{/isFile}}{{/isPrimitiveType}}| {{description}} |{{^required}} [optional]{{/required}}{{#defaultValue}} [default to {{defaultValue}}]{{/defaultValue}}{{#allowableValues}} [enum: {{#values}}{{{.}}}{{^@last}}, {{/@last}}{{/values}}]{{/allowableValues}} +{{#allParams}} **{{paramName}}** | {{#is this 'primitiveType'}}}**{{dataType}}**{{/is}}}{{#isNot this 'primitiveType'}}{{#is this 'file'}}**{{dataType}}**{{/is}}{{#isNot this 'file'}}{{#generateModelDocs}}[**{{dataType}}**]({{baseType}}.md){{/generateModelDocs}}{{^generateModelDocs}}**{{dataType}}**{{/generateModelDocs}}{{/isNot}}{{/isNot}}}| {{description}} |{{^required}} [optional]{{/required}}{{#defaultValue}} [default to {{defaultValue}}]{{/defaultValue}}{{#allowableValues}} [enum: {{#values}}{{{.}}}{{^@last}}, {{/@last}}{{/values}}]{{/allowableValues}} {{/allParams}} ### Return type @@ -58,8 +58,8 @@ Name | Type | Description | Notes ### HTTP request headers - - **Content-Type**: {{#consumes}}{{{mediaType}}}{{#hasMore}}, {{/hasMore}}{{/consumes}}{{^consumes}}Not defined{{/consumes}} - - **Accept**: {{#produces}}{{{mediaType}}}{{#hasMore}}, {{/hasMore}}{{/produces}}{{^produces}}Not defined{{/produces}} + - **Content-Type**: {{#consumes}}{{{mediaType}}}{{#has this 'more'}}, {{/has}}{{/consumes}}{{^consumes}}Not defined{{/consumes}} + - **Accept**: {{#produces}}{{{mediaType}}}{{#has this 'more'}}, {{/has}}{{/produces}}{{^produces}}Not defined{{/produces}} {{/operation}} {{/operations}} diff --git a/src/main/resources/v2/kotlin-server/class_doc.mustache b/src/main/resources/v2/kotlin-server/class_doc.mustache index e44b2c7269..18812e09f9 100644 --- a/src/main/resources/v2/kotlin-server/class_doc.mustache +++ b/src/main/resources/v2/kotlin-server/class_doc.mustache @@ -3,13 +3,13 @@ ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -{{#vars}}**{{name}}** | {{#isEnum}}[**inline**](#{{datatypeWithEnum}}){{/isEnum}}{{^isEnum}}{{#isPrimitiveType}}**{{datatype}}**{{/isPrimitiveType}}{{^isPrimitiveType}}[**{{datatype}}**]({{complexType}}.md){{/isPrimitiveType}}{{/isEnum}} | {{description}} | {{^required}} [optional]{{/required}}{{#readOnly}} [readonly]{{/readOnly}} +{{#vars}}**{{name}}** | {{#is this 'enum'}}[**inline**](#{{datatypeWithEnum}}){{/is}}{{#isNot this 'enum'}}{{#is this 'primitiveType'}}}**{{datatype}}**{{/is}}}{{#isNot this 'primitiveType'}}[**{{datatype}}**]({{complexType}}.md){{/isNot}}}{{/isNot}} | {{description}} | {{^required}} [optional]{{/required}}{{#readOnly}} [readonly]{{/readOnly}} {{/vars}} -{{#vars}}{{#isEnum}} +{{#vars}}{{#is this 'enum'}} {{!NOTE: see java's resources "pojo_doc.mustache" once enums are fully implemented}} ## Enum: {{baseName}} Name | Value ---- | -----{{#allowableValues}} {{name}} | {{#values}}{{.}}{{^@last}}, {{/@last}}{{/values}}{{/allowableValues}} -{{/isEnum}}{{/vars}} +{{/is}}{{/vars}} diff --git a/src/main/resources/v2/kotlin-server/data_class.mustache b/src/main/resources/v2/kotlin-server/data_class.mustache index 339e637555..41154e49fe 100644 --- a/src/main/resources/v2/kotlin-server/data_class.mustache +++ b/src/main/resources/v2/kotlin-server/data_class.mustache @@ -11,7 +11,7 @@ data class {{classname}} ( {{/hasOptional}}{{/hasRequired}}{{#optionalVars}}{{>data_class_opt_var}}{{^@last}}, {{/@last}}{{/optionalVars}} ) { -{{#hasEnums}}{{#vars}}{{#isEnum}} +{{#hasEnums}}{{#vars}}{{#is this 'enum'}} /** * {{{description}}} * Values: {{#allowableValues}}{{#enumVars}}{{&name}}{{^@last}},{{/@last}}{{/enumVars}}{{/allowableValues}} @@ -21,5 +21,5 @@ data class {{classname}} ( {{&name}}({{{value}}}){{^@last}},{{/@last}}{{#@last}};{{/@last}} {{/enumVars}}{{/allowableValues}} } -{{/isEnum}}{{/vars}}{{/hasEnums}} +{{/is}}{{/vars}}{{/hasEnums}} } diff --git a/src/main/resources/v2/kotlin-server/data_class_opt_var.mustache b/src/main/resources/v2/kotlin-server/data_class_opt_var.mustache index a88761ea90..7b1c72aa76 100644 --- a/src/main/resources/v2/kotlin-server/data_class_opt_var.mustache +++ b/src/main/resources/v2/kotlin-server/data_class_opt_var.mustache @@ -1,4 +1,4 @@ {{#description}} /* {{{description}}} */ {{/description}} - val {{{name}}}: {{#isEnum}}{{classname}}.{{nameInCamelCase}}{{/isEnum}}{{^isEnum}}{{{datatype}}}{{/isEnum}}? = {{#defaultvalue}}{{defaultvalue}}{{/defaultvalue}}{{^defaultvalue}}null{{/defaultvalue}} \ No newline at end of file + val {{{name}}}: {{#is this 'enum'}}{{classname}}.{{nameInCamelCase}}{{/is}}{{#isNot this 'enum'}}{{{datatype}}}{{/isNot}}? = {{#defaultvalue}}{{defaultvalue}}{{/defaultvalue}}{{^defaultvalue}}null{{/defaultvalue}} \ No newline at end of file diff --git a/src/main/resources/v2/kotlin-server/data_class_req_var.mustache b/src/main/resources/v2/kotlin-server/data_class_req_var.mustache index 8a33a15a18..659ceefc24 100644 --- a/src/main/resources/v2/kotlin-server/data_class_req_var.mustache +++ b/src/main/resources/v2/kotlin-server/data_class_req_var.mustache @@ -1,4 +1,4 @@ {{#description}} /* {{{description}}} */ {{/description}} - val {{{name}}}: {{#isEnum}}{{classname}}.{{nameInCamelCase}}{{/isEnum}}{{^isEnum}}{{{datatype}}}{{/isEnum}} \ No newline at end of file + val {{{name}}}: {{#is this 'enum'}}{{classname}}.{{nameInCamelCase}}{{/is}}{{#isNot this 'enum'}}{{{datatype}}}{{/isNot}} \ No newline at end of file diff --git a/src/main/resources/v2/kotlin-server/libraries/ktor/Configuration.kt.mustache b/src/main/resources/v2/kotlin-server/libraries/ktor/Configuration.kt.mustache index 1afaa5ff9e..2c43f79de4 100644 --- a/src/main/resources/v2/kotlin-server/libraries/ktor/Configuration.kt.mustache +++ b/src/main/resources/v2/kotlin-server/libraries/ktor/Configuration.kt.mustache @@ -88,8 +88,8 @@ val ApplicationAuthProviders: Map = listOfenum_class}}{{/isEnum}}{{^isEnum}}{{>data_class}}{{/isEnum}} +{{#is this 'enum'}}{{>enum_class}}{{/is}}{{#isNot this 'enum'}}{{>data_class}}{{/isNot}} {{/model}} {{/models}} diff --git a/src/main/resources/v2/kotlin-server/model_doc.mustache b/src/main/resources/v2/kotlin-server/model_doc.mustache index e3b7184211..e11fac0390 100644 --- a/src/main/resources/v2/kotlin-server/model_doc.mustache +++ b/src/main/resources/v2/kotlin-server/model_doc.mustache @@ -1,3 +1,3 @@ {{#models}}{{#model}} -{{#isEnum}}{{>enum_doc}}{{/isEnum}}{{^isEnum}}{{>class_doc}}{{/isEnum}} +{{#is this 'enum'}}{{>enum_doc}}{{/is}}{{#isNot this 'enum'}}{{>class_doc}}{{/isNot}} {{/model}}{{/models}} From f08aff7e2715f54e24022682515ee8b798c4dcab Mon Sep 17 00:00:00 2001 From: tcslater Date: Mon, 26 Feb 2018 09:59:09 +1100 Subject: [PATCH 06/34] Added kotlin codegen classes to service registry --- .../META-INF/services/io.swagger.codegen.CodegenConfig | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/resources/META-INF/services/io.swagger.codegen.CodegenConfig b/src/main/resources/META-INF/services/io.swagger.codegen.CodegenConfig index 281afd90b3..dcd24fcd0b 100644 --- a/src/main/resources/META-INF/services/io.swagger.codegen.CodegenConfig +++ b/src/main/resources/META-INF/services/io.swagger.codegen.CodegenConfig @@ -1,2 +1,4 @@ io.swagger.codegen.languages.java.JavaClientCodegen -io.swagger.codegen.languages.java.JavaInflectorServerCodegen \ No newline at end of file +io.swagger.codegen.languages.java.JavaInflectorServerCodegen +io.swagger.codegen.languages.kotlin.KotlinClientCodegen +io.swagger.codegen.languages.kotlin.KotlinServerCodegen \ No newline at end of file From 48f38f3e336577f810afeb06b5b5d5885a770702 Mon Sep 17 00:00:00 2001 From: tcslater Date: Mon, 26 Feb 2018 10:00:28 +1100 Subject: [PATCH 07/34] Generate oneOf/anyOf/allOf/not attributes as kotlin.Any type --- .../languages/kotlin/AbstractKotlinCodegen.java | 17 +++++++++++------ .../kotlin/KotlinClientCodegenModelTest.java | 12 +++++------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/main/java/io/swagger/codegen/languages/kotlin/AbstractKotlinCodegen.java b/src/main/java/io/swagger/codegen/languages/kotlin/AbstractKotlinCodegen.java index 8310d5a766..310a393133 100644 --- a/src/main/java/io/swagger/codegen/languages/kotlin/AbstractKotlinCodegen.java +++ b/src/main/java/io/swagger/codegen/languages/kotlin/AbstractKotlinCodegen.java @@ -36,6 +36,7 @@ public AbstractKotlinCodegen() { supportsInheritance = true; languageSpecificPrimitives = new HashSet(Arrays.asList( + "kotlin.Any", "kotlin.Byte", "kotlin.Short", "kotlin.Int", @@ -45,7 +46,7 @@ public AbstractKotlinCodegen() { "kotlin.Boolean", "kotlin.Char", "kotlin.String", - "kotlin.Array", + "kotlin.Array", "kotlin.collections.List", "kotlin.collections.Map", "kotlin.collections.Set" @@ -292,9 +293,7 @@ public String getSchemaType(Schema schema) { if (schema.getName() != null) { LOGGER.warn("No Type defined for Property " + schema.getName()); } else { - // LOGGER.error(schema.toString()); - // LOGGER.error("No Type defined.", new Exception()); - return "XXXXXX"; + return toModelName("kotlin.Any"); } } return toModelName(schemaType); @@ -489,6 +488,11 @@ public String toModelName(final String name) { return titleCase(modifiedName); } + @Override + public String toVarName(String name) { + return super.toVarName(sanitizeKotlinSpecificNames(name)); + } + /** * Provides a strongly typed declaration for simple arrays of some type and arrays of arrays of some type. * @@ -505,7 +509,7 @@ private String getArrayTypeDeclaration(ArraySchema arraySchema) { // TODO: We may want to differentiate here between generics and primitive arrays. instantiationType.append("<").append(nestedType).append(">"); return instantiationType.toString(); - } + } /** * Sanitize against Kotlin specific naming conventions, which may differ from those required by {@link DefaultCodegen#sanitizeName}. @@ -514,7 +518,8 @@ private String getArrayTypeDeclaration(ArraySchema arraySchema) { * @return sanitized string */ private String sanitizeKotlinSpecificNames(final String name) { - String word = name; + String word = removeNonNameElementToCamelCase(name); + for (Map.Entry specialCharacters : specialCharReplacements.entrySet()) { // Underscore is the only special character we'll allow if (!specialCharacters.getKey().equals("_")) { diff --git a/src/test/java/io/swagger/codegen/languages/kotlin/KotlinClientCodegenModelTest.java b/src/test/java/io/swagger/codegen/languages/kotlin/KotlinClientCodegenModelTest.java index e16bd3b9b6..40d1fc56ec 100644 --- a/src/test/java/io/swagger/codegen/languages/kotlin/KotlinClientCodegenModelTest.java +++ b/src/test/java/io/swagger/codegen/languages/kotlin/KotlinClientCodegenModelTest.java @@ -49,11 +49,11 @@ private Schema getSimpleSchema() { .description("a sample model") .addProperties("id", new IntegerSchema() .format(SchemaTypeUtil.INTEGER64_FORMAT)) - .addProperties("name", new StringSchema() + .addProperties("first-name", new StringSchema() .example("Tony")) .addProperties("createdAt", new DateTimeSchema()) .addRequiredItem("id") - .addRequiredItem("name"); + .addRequiredItem("first-name"); } // private Schema getMapSchema() { @@ -92,9 +92,9 @@ public void simpleModelTest() { Assert.assertTrue(getBooleanValue(property1, CodegenConstants.IS_NOT_CONTAINER_EXT_NAME)); final CodegenProperty property2 = cm.vars.get(1); - Assert.assertEquals(property2.baseName, "name"); + Assert.assertEquals(property2.baseName, "first-name"); Assert.assertEquals(property2.datatype, "kotlin.String"); - Assert.assertEquals(property2.name, "name"); + Assert.assertEquals(property2.name, "firstName"); Assert.assertEquals(property2.defaultValue, "null"); Assert.assertEquals(property2.baseType, "kotlin.String"); Assert.assertTrue(getBooleanValue(property2, CodegenConstants.HAS_MORE_EXT_NAME)); @@ -186,9 +186,7 @@ public void arrayPropertyTest() { Assert.assertEquals(generated.vars.size(), 0); Assert.assertEquals(generated.parent, "kotlin.Array"); - Assert.assertEquals(generated.imports.size(), 1); - - LOGGER.error(generated.imports.toArray()[0].toString()); + Assert.assertEquals(generated.imports.size(), 1); // final CodegenProperty property = generated.vars.get(0); // Assert.assertEquals(property.baseName, "examples"); From 454e20230fb6bcca6e0517384c69346c42cf2804 Mon Sep 17 00:00:00 2001 From: tcslater Date: Mon, 26 Feb 2018 13:36:58 +1100 Subject: [PATCH 08/34] Initial implementation of type aliases support in kotlin generator --- .../codegen/languages/kotlin/AbstractKotlinCodegen.java | 5 +++-- src/main/resources/v2/kotlin-client/model.mustache | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/swagger/codegen/languages/kotlin/AbstractKotlinCodegen.java b/src/main/java/io/swagger/codegen/languages/kotlin/AbstractKotlinCodegen.java index 310a393133..97730af517 100644 --- a/src/main/java/io/swagger/codegen/languages/kotlin/AbstractKotlinCodegen.java +++ b/src/main/java/io/swagger/codegen/languages/kotlin/AbstractKotlinCodegen.java @@ -1,5 +1,6 @@ package io.swagger.codegen.languages.kotlin; +import io.swagger.codegen.CodegenModel; import io.swagger.codegen.CliOption; import io.swagger.codegen.languages.DefaultCodegenConfig; import io.swagger.codegen.CodegenConstants; @@ -256,7 +257,7 @@ public void setEnumPropertyNaming(final String enumPropertyNamingType) { public String getTypeDeclaration(Schema propertySchema) { if (propertySchema instanceof ArraySchema) { return getArrayTypeDeclaration((ArraySchema) propertySchema); - } else if (propertySchema instanceof MapSchema) { + } else if (propertySchema instanceof MapSchema || propertySchema.getAdditionalProperties() != null) { Schema inner = (Schema) propertySchema.getAdditionalProperties(); if (inner == null) { LOGGER.warn(propertySchema.getName() + "(map property) does not have a proper inner type defined"); @@ -492,7 +493,7 @@ public String toModelName(final String name) { public String toVarName(String name) { return super.toVarName(sanitizeKotlinSpecificNames(name)); } - + /** * Provides a strongly typed declaration for simple arrays of some type and arrays of arrays of some type. * diff --git a/src/main/resources/v2/kotlin-client/model.mustache b/src/main/resources/v2/kotlin-client/model.mustache index 2d8ffcb343..852d62cdbd 100644 --- a/src/main/resources/v2/kotlin-client/model.mustache +++ b/src/main/resources/v2/kotlin-client/model.mustache @@ -9,6 +9,6 @@ import org.threeten.bp.LocalDateTime {{#models}} {{#model}} -{{#is this 'enum'}}{{>enum_class}}{{/is}}{{#isNot this 'enum'}}{{>data_class}}{{/isNot}} +{{#is this 'alias'}}typealias {{classname}} = {{dataType}}{{/is}}{{#isNot this 'alias'}}{{#is this 'enum'}}{{>enum_class}}{{/is}}{{#isNot this 'enum'}}{{>data_class}}{{/isNot}}{{/isNot}} {{/model}} {{/models}} From 745227c1e043156c7cc66c85724a650abdcb9bb5 Mon Sep 17 00:00:00 2001 From: tcslater Date: Mon, 26 Feb 2018 15:00:01 +1100 Subject: [PATCH 09/34] Initial implementation of type aliases support in kotlin-server generator --- src/main/resources/v2/kotlin-server/model.mustache | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/resources/v2/kotlin-server/model.mustache b/src/main/resources/v2/kotlin-server/model.mustache index 3ee6071019..852d62cdbd 100644 --- a/src/main/resources/v2/kotlin-server/model.mustache +++ b/src/main/resources/v2/kotlin-server/model.mustache @@ -3,9 +3,12 @@ package {{modelPackage}} {{#imports}}import {{import}} {{/imports}} +{{#threetenbp}} +import org.threeten.bp.LocalDateTime +{{/threetenbp}} {{#models}} {{#model}} -{{#is this 'enum'}}{{>enum_class}}{{/is}}{{#isNot this 'enum'}}{{>data_class}}{{/isNot}} +{{#is this 'alias'}}typealias {{classname}} = {{dataType}}{{/is}}{{#isNot this 'alias'}}{{#is this 'enum'}}{{>enum_class}}{{/is}}{{#isNot this 'enum'}}{{>data_class}}{{/isNot}}{{/isNot}} {{/model}} {{/models}} From 4e402db9db0815ae07b7088403ab0a7e485db432 Mon Sep 17 00:00:00 2001 From: tcslater Date: Mon, 26 Feb 2018 16:16:44 +1100 Subject: [PATCH 10/34] Migrating kotlin-server ktor templates --- .../libraries/ktor/Configuration.kt.mustache | 4 +-- .../libraries/ktor/README.mustache | 14 +++++----- .../libraries/ktor/_api_body.mustache | 18 ++++++------ .../libraries/ktor/_principal.mustache | 12 ++++---- .../kotlin-server/libraries/ktor/api.mustache | 28 +++++++++---------- .../libraries/ktor/application.conf.mustache | 4 +-- 6 files changed, 40 insertions(+), 40 deletions(-) diff --git a/src/main/resources/v2/kotlin-server/libraries/ktor/Configuration.kt.mustache b/src/main/resources/v2/kotlin-server/libraries/ktor/Configuration.kt.mustache index 2c43f79de4..db48170a28 100644 --- a/src/main/resources/v2/kotlin-server/libraries/ktor/Configuration.kt.mustache +++ b/src/main/resources/v2/kotlin-server/libraries/ktor/Configuration.kt.mustache @@ -79,7 +79,7 @@ internal fun ApplicationCompressionConfiguration(): Compression.Configuration.() // Defines authentication mechanisms used throughout the application. val ApplicationAuthProviders: Map = listOf( {{#authMethods}} - {{#isOAuth}} + {{#is this 'oauth'}} OAuthServerSettings.OAuth2ServerSettings( name = "{{name}}", authorizeUrl = "{{authorizationUrl}}", @@ -90,7 +90,7 @@ val ApplicationAuthProviders: Map = listOf ### {{name}} -{{#isApiKey}}- **Type**: API key +{{#is this 'api-key'}}- **Type**: API key - **API key parameter name**: {{keyParamName}} -- **Location**: {{#isKeyInQuery}}URL query string{{/isKeyInQuery}}{{#isKeyInHeader}}HTTP header{{/isKeyInHeader}} -{{/isApiKey}} -{{#isBasic}}- **Type**: HTTP basic authentication -{{/isBasic}} -{{#isOAuth}}- **Type**: OAuth +- **Location**: {{#is this 'key-in-query'}}URL query string{{/is}}{{#is this 'key-in-header'}}HTTP header{{/is}} +{{/is}} +{{#is this 'basic'}}- **Type**: HTTP basic authentication +{{/is}} +{{#is this 'oauth'}}- **Type**: OAuth - **Flow**: {{flow}} - **Authorization URL**: {{authorizationUrl}} - **Scopes**: {{^scopes}}N/A{{/scopes}} {{#scopes}} - {{scope}}: {{description}} {{/scopes}} -{{/isOAuth}} +{{/is}} {{/authMethods}} \ No newline at end of file diff --git a/src/main/resources/v2/kotlin-server/libraries/ktor/_api_body.mustache b/src/main/resources/v2/kotlin-server/libraries/ktor/_api_body.mustache index 9dcf500f49..6ed8842742 100644 --- a/src/main/resources/v2/kotlin-server/libraries/ktor/_api_body.mustache +++ b/src/main/resources/v2/kotlin-server/libraries/ktor/_api_body.mustache @@ -1,25 +1,25 @@ -{{#hasAuthMethods}} +{{#has this 'auth-methods'}} {{>libraries/ktor/_principal}} if (principal == null) { call.respond(HttpStatusCode.Unauthorized) } else { {{#examples}} - {{#-first}} - {{#lambda.indented}}{{>_response}}{{/lambda.indented}} - {{/-first}} + {{#@first}} + {{>_response}} + {{/@first}} {{/examples}} {{^examples}} call.respond(HttpStatusCode.NotImplemented) {{/examples}} } -{{/hasAuthMethods}} -{{^hasAuthMethods}} +{{/has}} +{{^notHas this 'auth-methods'}} {{#examples}} -{{#-first}} +{{#@first}} {{>libraries/ktor/_response}} -{{/-first}} +{{/@first}} {{/examples}} {{^examples}} call.respond(HttpStatusCode.NotImplemented) {{/examples}} -{{/hasAuthMethods}} \ No newline at end of file +{{/notHas}} \ No newline at end of file diff --git a/src/main/resources/v2/kotlin-server/libraries/ktor/_principal.mustache b/src/main/resources/v2/kotlin-server/libraries/ktor/_principal.mustache index d49c5d537f..edb7808391 100644 --- a/src/main/resources/v2/kotlin-server/libraries/ktor/_principal.mustache +++ b/src/main/resources/v2/kotlin-server/libraries/ktor/_principal.mustache @@ -1,11 +1,11 @@ {{#authMethods}} -{{#isBasic}} +{{#is this 'basic'}} val principal = call.authentication.principal() -{{/isBasic}} -{{#isApiKey}} +{{/is}} +{{#is this 'api-key'}} val principal = call.authentication.principal() -{{/isApiKey}} -{{#isOAuth}} +{{/is}} +{{#is this 'oauth'}} val principal = call.authentication.principal() -{{/isOAuth}} +{{/is}} {{/authMethods}} diff --git a/src/main/resources/v2/kotlin-server/libraries/ktor/api.mustache b/src/main/resources/v2/kotlin-server/libraries/ktor/api.mustache index 16b6b9fbf6..45a8b577a5 100644 --- a/src/main/resources/v2/kotlin-server/libraries/ktor/api.mustache +++ b/src/main/resources/v2/kotlin-server/libraries/ktor/api.mustache @@ -40,27 +40,27 @@ fun Route.{{classname}}() { {{#bodyAllowed}} route("{{path}}") { - {{#lambda.lowercase}}{{httpMethod}}{{/lambda.lowercase}} { - {{#lambda.indented_12}}{{>libraries/ktor/_api_body}}{{/lambda.indented_12}} + {{httpMethod}} { + {{>libraries/ktor/_api_body}} } } {{/bodyAllowed}} {{^bodyAllowed}} {{! NOTE: Locations can be used on routes without body parameters.}} - {{#lambda.lowercase}}{{httpMethod}}{{/lambda.lowercase}} { it: Paths.{{operationId}} -> - {{#lambda.indented_8}}{{>libraries/ktor/_api_body}}{{/lambda.indented_8}} + {{httpMethod}} { it: Paths.{{operationId}} -> + {{>libraries/ktor/_api_body}} } {{/bodyAllowed}} {{! THis looks a little weird, but it's completely valid Kotlin code, and simplifies templated route logic above. }} - {{#hasAuthMethods}}.apply { + {{#has this 'auth-methods'}}.apply { // TODO: ktor doesn't allow different authentication registrations for endpoints sharing the same path but different methods. // It could be the authentication block is being abused here. Until this is resolved, swallow duplicate exceptions. try { authentication { {{#authMethods}} - {{#isBasic}} + {{#is this 'basic'}} basicAuthentication("{{{name}}}") { credentials -> // TODO: "Apply your basic authentication functionality." // Accessible in-method via call.principal() @@ -70,8 +70,8 @@ fun Route.{{classname}}() { null } } - {{/isBasic}} - {{#isApiKey}} + {{/is}} + {{#is this 'api-key'}} // "Implement API key auth ({{{name}}}) for parameter name '{{{keyParamName}}}'." apiKeyAuth("{{{keyParamName}}}", {{#isKeyInQuery}}"query"{{/isKeyInQuery}}{{#isKeyInHeader}}"header"{{/isKeyInHeader}}) { // TODO: "Verify key here , accessible as it.value" @@ -81,8 +81,8 @@ fun Route.{{classname}}() { null } } - {{/isApiKey}} - {{#isOAuth}} + {{/is}} + {{#is this 'oauth'}} {{#bodyAllowed}} oauth(client, ApplicationExecutors.asCoroutineDispatcher(), { ApplicationAuthProviders["{{{name}}}"] }, { // TODO: define a callback url here. @@ -97,7 +97,7 @@ fun Route.{{classname}}() { "/" }) {{/bodyAllowed}} - {{/isOAuth}} + {{/is}} {{/authMethods}} } } catch(e: io.ktor.application.DuplicateApplicationFeatureException){ @@ -105,10 +105,10 @@ fun Route.{{classname}}() { "Generated endpoints may need to be merged under a 'route' entry.") } } - {{/hasAuthMethods}} - {{^hasAuthMethods}} + {{/has}} + {{^notHas this 'auth-methods'}} - {{/hasAuthMethods}} + {{/notHas}} {{/operation}} } {{/operations}} \ No newline at end of file diff --git a/src/main/resources/v2/kotlin-server/libraries/ktor/application.conf.mustache b/src/main/resources/v2/kotlin-server/libraries/ktor/application.conf.mustache index 032be42fa1..d303443788 100644 --- a/src/main/resources/v2/kotlin-server/libraries/ktor/application.conf.mustache +++ b/src/main/resources/v2/kotlin-server/libraries/ktor/application.conf.mustache @@ -16,12 +16,12 @@ ktor { auth { oauth { {{#authMethods}} -{{#isOAuth}} +{{#is this 'oauth'}} {{name}} { clientId = "" clientSecret = "" } -{{/isOAuth}} +{{/is}} {{/authMethods}} } } \ No newline at end of file From 9ee0cecadc380f7cb9c6d9358020b1569cfe18b7 Mon Sep 17 00:00:00 2001 From: tcslater Date: Mon, 26 Feb 2018 16:47:16 +1100 Subject: [PATCH 11/34] Migrating kotlin-server ktor templates --- .../resources/v2/kotlin-server/libraries/ktor/api.mustache | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/v2/kotlin-server/libraries/ktor/api.mustache b/src/main/resources/v2/kotlin-server/libraries/ktor/api.mustache index 45a8b577a5..63c1848493 100644 --- a/src/main/resources/v2/kotlin-server/libraries/ktor/api.mustache +++ b/src/main/resources/v2/kotlin-server/libraries/ktor/api.mustache @@ -40,7 +40,7 @@ fun Route.{{classname}}() { {{#bodyAllowed}} route("{{path}}") { - {{httpMethod}} { + {{httpMethodLowerCase}} { {{>libraries/ktor/_api_body}} } } @@ -48,7 +48,7 @@ fun Route.{{classname}}() { {{^bodyAllowed}} {{! NOTE: Locations can be used on routes without body parameters.}} - {{httpMethod}} { it: Paths.{{operationId}} -> + {{httpMethodLowerCase}} { it: Paths.{{operationId}} -> {{>libraries/ktor/_api_body}} } {{/bodyAllowed}} From 8f75ab481815b2a043c1bd05a7989de19c8a43ce Mon Sep 17 00:00:00 2001 From: tcslater Date: Mon, 26 Feb 2018 16:47:51 +1100 Subject: [PATCH 12/34] Migrating kotlin-server ktor templates --- .../languages/kotlin/KotlinServerCodegen.java | 25 ------------------- 1 file changed, 25 deletions(-) diff --git a/src/main/java/io/swagger/codegen/languages/kotlin/KotlinServerCodegen.java b/src/main/java/io/swagger/codegen/languages/kotlin/KotlinServerCodegen.java index 4ea3b645dd..b43c91e5c4 100644 --- a/src/main/java/io/swagger/codegen/languages/kotlin/KotlinServerCodegen.java +++ b/src/main/java/io/swagger/codegen/languages/kotlin/KotlinServerCodegen.java @@ -190,31 +190,6 @@ public void processOpts() { final String infrastructureFolder = (sourceFolder + File.separator + packageName + File.separator + "infrastructure").replace(".", File.separator); supportingFiles.add(new SupportingFile("ApiKeyAuth.kt.mustache", infrastructureFolder, "ApiKeyAuth.kt")); - - addMustacheLambdas(additionalProperties); - } - - private void addMustacheLambdas(Map objs) { - - // Map lambdas = new ImmutableMap.Builder() - // .put("lowercase", new LowercaseLambda().generator(this)) - // .put("uppercase", new UppercaseLambda()) - // .put("titlecase", new TitlecaseLambda()) - // .put("camelcase", new CamelCaseLambda().generator(this)) - // .put("indented", new IndentedLambda()) - // .put("indented_8", new IndentedLambda(8, " ")) - // .put("indented_12", new IndentedLambda(12, " ")) - // .put("indented_16", new IndentedLambda(16, " ")) - // .build(); - - // if (objs.containsKey("lambda")) { - // LOGGER.warn("An property named 'lambda' already exists. Mustache lambdas renamed from 'lambda' to '_lambda'. " + - // "You'll likely need to use a custom template, " + - // "see https://github.com/swagger-api/swagger-codegen#modifying-the-client-library-format. "); - // objs.put("_lambda", lambdas); - // } else { - // objs.put("lambda", lambdas); - // } } public static class Constants { From 1264aa2e73bb0cec7357284e619f535bea1fc3fe Mon Sep 17 00:00:00 2001 From: tcslater Date: Tue, 27 Feb 2018 11:14:38 +1100 Subject: [PATCH 13/34] Further fixes to template migration --- src/main/resources/v2/kotlin-client/data_class.mustache | 8 ++++---- src/main/resources/v2/kotlin-server/data_class.mustache | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/resources/v2/kotlin-client/data_class.mustache b/src/main/resources/v2/kotlin-client/data_class.mustache index 2429a754b6..42b0bb8fe0 100644 --- a/src/main/resources/v2/kotlin-client/data_class.mustache +++ b/src/main/resources/v2/kotlin-client/data_class.mustache @@ -7,11 +7,11 @@ data class {{classname}} ( {{#requiredVars}} {{>data_class_req_var}}{{^@last}}, -{{/@last}}{{/requiredVars}}{{#hasRequired}}{{#hasOptional}}, -{{/hasOptional}}{{/hasRequired}}{{#optionalVars}}{{>data_class_opt_var}}{{^@last}}, +{{/@last}}{{/requiredVars}}{{#has this 'required'}}{{#has this 'optional'}}, +{{/has}}{{/has}}{{#optionalVars}}{{>data_class_opt_var}}{{^@last}}, {{/@last}}{{/optionalVars}} ) { -{{#hasEnums}}{{#vars}}{{#is this 'enum'}} +{{#has this 'enums'}}{{#vars}}{{#is this 'enum'}} /** * {{{description}}} * Values: {{#allowableValues}}{{#enumVars}}{{&name}}{{^@last}},{{/@last}}{{/enumVars}}{{/allowableValues}} @@ -21,5 +21,5 @@ data class {{classname}} ( {{&name}}({{{value}}}){{^@last}},{{/@last}}{{#@last}};{{/@last}} {{/enumVars}}{{/allowableValues}} } -{{/is}}{{/vars}}{{/hasEnums}} +{{/is}}{{/vars}}{{/has}} } diff --git a/src/main/resources/v2/kotlin-server/data_class.mustache b/src/main/resources/v2/kotlin-server/data_class.mustache index 41154e49fe..de71a9556a 100644 --- a/src/main/resources/v2/kotlin-server/data_class.mustache +++ b/src/main/resources/v2/kotlin-server/data_class.mustache @@ -7,11 +7,11 @@ data class {{classname}} ( {{#requiredVars}} {{>data_class_req_var}}{{^@last}}, -{{/@last}}{{/requiredVars}}{{#hasRequired}}{{#hasOptional}}, -{{/hasOptional}}{{/hasRequired}}{{#optionalVars}}{{>data_class_opt_var}}{{^@last}}, +{{/@last}}{{/requiredVars}}{{#has this 'required'}}{{#has this 'optional'}}, +{{/has}}{{/has}}{{#optionalVars}}{{>data_class_opt_var}}{{^@last}}, {{/@last}}{{/optionalVars}} ) { -{{#hasEnums}}{{#vars}}{{#is this 'enum'}} +{{#has this 'enums'}}{{#vars}}{{#is this 'enum'}} /** * {{{description}}} * Values: {{#allowableValues}}{{#enumVars}}{{&name}}{{^@last}},{{/@last}}{{/enumVars}}{{/allowableValues}} @@ -21,5 +21,5 @@ data class {{classname}} ( {{&name}}({{{value}}}){{^@last}},{{/@last}}{{#@last}};{{/@last}} {{/enumVars}}{{/allowableValues}} } -{{/is}}{{/vars}}{{/hasEnums}} +{{/is}}{{/vars}}{{/has}} } From 9abfda772c3b2dcf7566a63d5c6605c2d3488a0e Mon Sep 17 00:00:00 2001 From: tcslater Date: Tue, 27 Feb 2018 11:16:40 +1100 Subject: [PATCH 14/34] Remove custom httpMethodLowerCase property. Register and use Handlebars String helpers instead --- .../languages/kotlin/KotlinServerCodegen.java | 12 ++++++++---- .../v2/kotlin-server/libraries/ktor/api.mustache | 4 ++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/main/java/io/swagger/codegen/languages/kotlin/KotlinServerCodegen.java b/src/main/java/io/swagger/codegen/languages/kotlin/KotlinServerCodegen.java index b43c91e5c4..924d3032b4 100644 --- a/src/main/java/io/swagger/codegen/languages/kotlin/KotlinServerCodegen.java +++ b/src/main/java/io/swagger/codegen/languages/kotlin/KotlinServerCodegen.java @@ -1,20 +1,18 @@ package io.swagger.codegen.languages.kotlin; import com.google.common.collect.ImmutableMap; -// import com.samskivert.mustache.Mustache; import io.swagger.codegen.CliOption; import io.swagger.codegen.CodegenConstants; import io.swagger.codegen.CodegenType; import io.swagger.codegen.SupportingFile; -// import io.swagger.codegen.mustache.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import java.io.File; import java.util.Arrays; import java.util.List; import java.util.Map; - +import com.github.jknack.handlebars.helper.StringHelpers; +import com.github.jknack.handlebars.Handlebars; import org.apache.commons.lang3.StringUtils; public class KotlinServerCodegen extends AbstractKotlinCodegen { @@ -192,6 +190,12 @@ public void processOpts() { supportingFiles.add(new SupportingFile("ApiKeyAuth.kt.mustache", infrastructureFolder, "ApiKeyAuth.kt")); } + @Override + public void addHandlebarHelpers(Handlebars handlebars) { + super.addHandlebarHelpers(handlebars); + handlebars.registerHelpers(StringHelpers.class); + } + public static class Constants { public final static String KTOR = "ktor"; public final static String AUTOMATIC_HEAD_REQUESTS = "featureAutoHead"; diff --git a/src/main/resources/v2/kotlin-server/libraries/ktor/api.mustache b/src/main/resources/v2/kotlin-server/libraries/ktor/api.mustache index 63c1848493..d04ffb6db2 100644 --- a/src/main/resources/v2/kotlin-server/libraries/ktor/api.mustache +++ b/src/main/resources/v2/kotlin-server/libraries/ktor/api.mustache @@ -40,7 +40,7 @@ fun Route.{{classname}}() { {{#bodyAllowed}} route("{{path}}") { - {{httpMethodLowerCase}} { + {{lower httpMethod}} { {{>libraries/ktor/_api_body}} } } @@ -48,7 +48,7 @@ fun Route.{{classname}}() { {{^bodyAllowed}} {{! NOTE: Locations can be used on routes without body parameters.}} - {{httpMethodLowerCase}} { it: Paths.{{operationId}} -> + {{lower httpMethod}} { it: Paths.{{operationId}} -> {{>libraries/ktor/_api_body}} } {{/bodyAllowed}} From 8dd95bc9d08cc9c5e8a43a4fc84c3a2fd77613cf Mon Sep 17 00:00:00 2001 From: tcslater Date: Tue, 27 Feb 2018 11:50:53 +1100 Subject: [PATCH 15/34] Removed commented out code --- .../codegen/languages/kotlin/KotlinServerCodegen.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/main/java/io/swagger/codegen/languages/kotlin/KotlinServerCodegen.java b/src/main/java/io/swagger/codegen/languages/kotlin/KotlinServerCodegen.java index 924d3032b4..ba11bb3a36 100644 --- a/src/main/java/io/swagger/codegen/languages/kotlin/KotlinServerCodegen.java +++ b/src/main/java/io/swagger/codegen/languages/kotlin/KotlinServerCodegen.java @@ -164,7 +164,6 @@ public void processOpts() { additionalProperties.put(Constants.COMPRESSION, getCompressionFeatureEnabled()); } - // Boolean generateApis = additionalProperties.containsKey(CodegenConstants.GENERATE_APIS) && (Boolean)additionalProperties.get(CodegenConstants.GENERATE_APIS); String packageFolder = (sourceFolder + File.separator + packageName).replace(".", File.separator); String resourcesFolder = "src/main/resources"; // not sure this can be user configurable. @@ -177,10 +176,8 @@ public void processOpts() { supportingFiles.add(new SupportingFile("AppMain.kt.mustache", packageFolder, "AppMain.kt")); supportingFiles.add(new SupportingFile("Configuration.kt.mustache", packageFolder, "Configuration.kt")); - - // if (generateApis) { - supportingFiles.add(new SupportingFile("Paths.kt.mustache", packageFolder, "Paths.kt")); - // } + + supportingFiles.add(new SupportingFile("Paths.kt.mustache", packageFolder, "Paths.kt")); supportingFiles.add(new SupportingFile("application.conf.mustache", resourcesFolder, "application.conf")); supportingFiles.add(new SupportingFile("logback.xml", resourcesFolder, "logback.xml")); From 9dcd1902a4bf40d0a575b0bf536711d40794a31e Mon Sep 17 00:00:00 2001 From: tcslater Date: Tue, 27 Feb 2018 12:05:02 +1100 Subject: [PATCH 16/34] Fixing constants in options test --- .../codegen/options/KotlinClientCodegenOptionsProvider.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/java/io/swagger/codegen/options/KotlinClientCodegenOptionsProvider.java b/src/test/java/io/swagger/codegen/options/KotlinClientCodegenOptionsProvider.java index b0c126da72..98a8388d20 100644 --- a/src/test/java/io/swagger/codegen/options/KotlinClientCodegenOptionsProvider.java +++ b/src/test/java/io/swagger/codegen/options/KotlinClientCodegenOptionsProvider.java @@ -1,6 +1,7 @@ package io.swagger.codegen.options; import io.swagger.codegen.CodegenConstants; +import io.swagger.codegen.languages.kotlin.KotlinClientCodegen; import com.google.common.collect.ImmutableMap; @@ -13,7 +14,7 @@ public class KotlinClientCodegenOptionsProvider implements OptionsProvider { public static final String GROUP_ID = "io.swagger.tests"; public static final String SOURCE_FOLDER = "./generated/kotlin"; public static final String ENUM_PROPERTY_NAMING = "camelCase"; - public static final String DATE_LIBRARY = "dateLibrary"; + public static final String DATE_LIBRARY = KotlinClientCodegen.DateLibrary.JAVA8.value; @Override public String getLanguage() { @@ -30,7 +31,7 @@ public Map createOptions() { .put(CodegenConstants.GROUP_ID, GROUP_ID) .put(CodegenConstants.SOURCE_FOLDER, SOURCE_FOLDER) .put(CodegenConstants.ENUM_PROPERTY_NAMING, ENUM_PROPERTY_NAMING) - .put("dateLibrary", DATE_LIBRARY) + .put(KotlinClientCodegen.DATE_LIBRARY, DATE_LIBRARY) .build(); } From e364587f38f85cb7bb79694df42807a1535b5492 Mon Sep 17 00:00:00 2001 From: tcslater Date: Tue, 27 Feb 2018 13:49:08 +1100 Subject: [PATCH 17/34] Fixed array model test --- .../kotlin/KotlinClientCodegenModelTest.java | 102 +++++++++--------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/src/test/java/io/swagger/codegen/languages/kotlin/KotlinClientCodegenModelTest.java b/src/test/java/io/swagger/codegen/languages/kotlin/KotlinClientCodegenModelTest.java index 40d1fc56ec..aeb03a4ef0 100644 --- a/src/test/java/io/swagger/codegen/languages/kotlin/KotlinClientCodegenModelTest.java +++ b/src/test/java/io/swagger/codegen/languages/kotlin/KotlinClientCodegenModelTest.java @@ -38,10 +38,13 @@ public class KotlinClientCodegenModelTest { protected static final Logger LOGGER = LoggerFactory.getLogger(KotlinClientCodegenModelTest.class); private Schema getArrayTestSchema() { - return new ArraySchema() - .items(new StringSchema()) - .description("a sample model") - .name("arraySchema"); + final Schema propertySchema = new ArraySchema() + .items(new StringSchema()) + .description("an array property"); + return new Schema() + .type("object") + .description("a sample model") + .addProperties("examples", propertySchema); } private Schema getSimpleSchema() { @@ -56,17 +59,17 @@ private Schema getSimpleSchema() { .addRequiredItem("first-name"); } - // private Schema getMapSchema() { - // return new Schema() - // .description("a sample model") - // .additionalProperties(new Schema().$ref("#/components/schemas/Children")); - // } + private Schema getMapSchema() { + return new Schema() + .description("a sample model") + .additionalProperties(new Schema().$ref("#/components/schemas/Children")); + } - // private Schema getComplexSchema() { - // return new Schema() - // .description("a sample model") - // .additionalProperties(new Schema().$ref("#/definitions/Child")); - // } + private Schema getComplexSchema() { + return new Schema() + .description("a sample model") + .additionalProperties(new Schema().$ref("#/definitions/Child")); + } @Test(description = "convert a simple model") public void simpleModelTest() { @@ -110,7 +113,7 @@ public void simpleModelTest() { Assert.assertEquals(property3.baseType, "java.time.LocalDateTime"); Assert.assertFalse(getBooleanValue(property3, CodegenConstants.HAS_MORE_EXT_NAME)); Assert.assertFalse(property3.required); - Assert.assertTrue(getBooleanValue(property3, CodegenConstants.IS_NOT_CONTAINER_EXT_NAME)); + Assert.assertTrue(getBooleanValue(property3, CodegenConstants.IS_NOT_CONTAINER_EXT_NAME)); } @Test(description = "convert a simple model: threetenbp") @@ -183,45 +186,42 @@ public void arrayPropertyTest() { Assert.assertEquals(generated.name, "sample"); Assert.assertEquals(generated.classname, "Sample"); Assert.assertEquals(generated.description, "a sample model"); - Assert.assertEquals(generated.vars.size(), 0); - - Assert.assertEquals(generated.parent, "kotlin.Array"); - Assert.assertEquals(generated.imports.size(), 1); - - // final CodegenProperty property = generated.vars.get(0); - // Assert.assertEquals(property.baseName, "examples"); - // Assert.assertEquals(property.getter, "getExamples"); - // Assert.assertEquals(property.setter, "setExamples"); - // // Assert.assertEquals(property.datatype, "kotlin.Array"); - // Assert.assertEquals(property.name, "examples"); - // Assert.assertEquals(property.defaultValue, "null"); - // Assert.assertEquals(property.baseType, "kotlin.Array"); - // Assert.assertEquals(property.containerType, "array"); - // Assert.assertFalse(property.required); - // Assert.assertTrue(getBooleanValue(property, CodegenConstants.IS_CONTAINER_EXT_NAME)); + Assert.assertEquals(generated.vars.size(), 1); + + final CodegenProperty property = generated.vars.get(0); + Assert.assertEquals(property.baseName, "examples"); + Assert.assertEquals(property.getter, "getExamples"); + Assert.assertEquals(property.setter, "setExamples"); + Assert.assertEquals(property.datatype, "kotlin.Array"); + Assert.assertEquals(property.name, "examples"); + Assert.assertEquals(property.defaultValue, "null"); + Assert.assertEquals(property.baseType, "kotlin.Array"); + Assert.assertEquals(property.containerType, "array"); + Assert.assertFalse(property.required); + Assert.assertTrue(getBooleanValue(property, CodegenConstants.IS_CONTAINER_EXT_NAME)); } - // @Test(description = "convert a model with a map property") - // public void mapPropertyTest() { - // final Schema schema = getMapSchema(); - // final CodegenConfig codegen = new KotlinClientCodegen(); - // final CodegenModel cm = codegen.fromModel("sample", schema); - - // Assert.assertEquals(cm.name, "sample"); - // Assert.assertEquals(cm.classname, "Sample"); - // Assert.assertEquals(cm.description, "a sample model"); - // // Assert.assertEquals(cm.vars.size(), 1); + @Test(description = "convert a model with a map property") + public void mapPropertyTest() { + final Schema schema = getMapSchema(); + final CodegenConfig codegen = new KotlinClientCodegen(); + final CodegenModel cm = codegen.fromModel("sample", schema); - // // final CodegenProperty property1 = cm.vars.get(0); - // // Assert.assertEquals(property1.baseName, "mapping"); - // // Assert.assertEquals(property1.datatype, "kotlin.collections.Map"); - // // Assert.assertEquals(property1.name, "mapping"); - // // Assert.assertEquals(property1.baseType, "kotlin.collections.Map"); - // // Assert.assertEquals(property1.containerType, "map"); - // // Assert.assertFalse(property1.required); - // // Assert.assertTrue(getBooleanValue(property1, CodegenConstants.IS_CONTAINER_EXT_NAME)); - // // Assert.assertTrue(getBooleanValue(property1, CodegenConstants.IS_PRIMITIVE_TYPE_EXT_NAME)); - // } + Assert.assertEquals(cm.name, "sample"); + Assert.assertEquals(cm.classname, "Sample"); + Assert.assertEquals(cm.description, "a sample model"); + // Assert.assertEquals(cm.vars.size(), 1); + + // final CodegenProperty property1 = cm.vars.get(0); + // Assert.assertEquals(property1.baseName, "mapping"); + // Assert.assertEquals(property1.datatype, "kotlin.collections.Map"); + // Assert.assertEquals(property1.name, "mapping"); + // Assert.assertEquals(property1.baseType, "kotlin.collections.Map"); + // Assert.assertEquals(property1.containerType, "map"); + // Assert.assertFalse(property1.required); + // Assert.assertTrue(getBooleanValue(property1, CodegenConstants.IS_CONTAINER_EXT_NAME)); + // Assert.assertTrue(getBooleanValue(property1, CodegenConstants.IS_PRIMITIVE_TYPE_EXT_NAME)); + } // @Test(description = "convert a model with complex property") // public void complexPropertyTest() { From 4adf9a3782b280b2cc0cb7d7d2f80b7fdaad4563 Mon Sep 17 00:00:00 2001 From: tcslater Date: Tue, 27 Feb 2018 16:11:43 +1100 Subject: [PATCH 18/34] Fix map generation --- .../codegen/languages/kotlin/AbstractKotlinCodegen.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/swagger/codegen/languages/kotlin/AbstractKotlinCodegen.java b/src/main/java/io/swagger/codegen/languages/kotlin/AbstractKotlinCodegen.java index 97730af517..a766e78212 100644 --- a/src/main/java/io/swagger/codegen/languages/kotlin/AbstractKotlinCodegen.java +++ b/src/main/java/io/swagger/codegen/languages/kotlin/AbstractKotlinCodegen.java @@ -20,6 +20,7 @@ public abstract class AbstractKotlinCodegen extends DefaultCodegenConfig { static Logger LOGGER = LoggerFactory.getLogger(AbstractKotlinCodegen.class); + protected String artifactId; protected String artifactVersion = "1.0.0"; protected String groupId = "io.swagger"; @@ -264,9 +265,8 @@ public String getTypeDeclaration(Schema propertySchema) { // TODO maybe better defaulting to StringProperty than returning null return null; } - return String.format("%s<%s>", getSchemaType(propertySchema), getTypeDeclaration(inner)); // Maps will be keyed only by primitive Kotlin string - // return getSwaggerType(p) + ""; + return String.format("%s", getSchemaType(propertySchema), getTypeDeclaration(inner)); } return super.getTypeDeclaration(propertySchema); } From 3d8c13b79502744e99de9be0c640ad6c8b37eb9c Mon Sep 17 00:00:00 2001 From: tcslater Date: Tue, 27 Feb 2018 16:12:19 +1100 Subject: [PATCH 19/34] Update map generation test to use schemas --- .../kotlin/KotlinClientCodegenModelTest.java | 39 ++++++++++++------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/src/test/java/io/swagger/codegen/languages/kotlin/KotlinClientCodegenModelTest.java b/src/test/java/io/swagger/codegen/languages/kotlin/KotlinClientCodegenModelTest.java index aeb03a4ef0..6fb424efb7 100644 --- a/src/test/java/io/swagger/codegen/languages/kotlin/KotlinClientCodegenModelTest.java +++ b/src/test/java/io/swagger/codegen/languages/kotlin/KotlinClientCodegenModelTest.java @@ -60,9 +60,22 @@ private Schema getSimpleSchema() { } private Schema getMapSchema() { - return new Schema() + // return new ModelImpl() + // .description("a sample model") + // .property("mapping", new MapProperty() + // .additionalProperties(new StringProperty())) + // .required("id"); + + // final Schema propertySchema = new MapSchema().additionalProperties(new StringSchema()); + // return new Schema() + // .type("object") + // .description("a sample model") + // .addProperties("mapping", propertySchema); + return new Schema() .description("a sample model") - .additionalProperties(new Schema().$ref("#/components/schemas/Children")); + .addProperties("mapping", new MapSchema() + .additionalProperties(new StringSchema())) + .addRequiredItem("id"); } private Schema getComplexSchema() { @@ -210,17 +223,17 @@ public void mapPropertyTest() { Assert.assertEquals(cm.name, "sample"); Assert.assertEquals(cm.classname, "Sample"); Assert.assertEquals(cm.description, "a sample model"); - // Assert.assertEquals(cm.vars.size(), 1); - - // final CodegenProperty property1 = cm.vars.get(0); - // Assert.assertEquals(property1.baseName, "mapping"); - // Assert.assertEquals(property1.datatype, "kotlin.collections.Map"); - // Assert.assertEquals(property1.name, "mapping"); - // Assert.assertEquals(property1.baseType, "kotlin.collections.Map"); - // Assert.assertEquals(property1.containerType, "map"); - // Assert.assertFalse(property1.required); - // Assert.assertTrue(getBooleanValue(property1, CodegenConstants.IS_CONTAINER_EXT_NAME)); - // Assert.assertTrue(getBooleanValue(property1, CodegenConstants.IS_PRIMITIVE_TYPE_EXT_NAME)); + Assert.assertEquals(cm.vars.size(), 1); + + final CodegenProperty property1 = cm.vars.get(0); + Assert.assertEquals(property1.baseName, "mapping"); + Assert.assertEquals(property1.datatype, "kotlin.collections.Map"); + Assert.assertEquals(property1.name, "mapping"); + Assert.assertEquals(property1.baseType, "kotlin.collections.Map"); + Assert.assertEquals(property1.containerType, "map"); + Assert.assertFalse(property1.required); + Assert.assertTrue(getBooleanValue(property1, CodegenConstants.IS_CONTAINER_EXT_NAME)); + Assert.assertTrue(getBooleanValue(property1, CodegenConstants.IS_PRIMITIVE_TYPE_EXT_NAME)); } // @Test(description = "convert a model with complex property") From 285d415c0fa35168ae6e55438ced8744dca73018 Mon Sep 17 00:00:00 2001 From: tcslater Date: Tue, 27 Feb 2018 16:44:58 +1100 Subject: [PATCH 20/34] Fix complex model test --- .../kotlin/KotlinClientCodegenModelTest.java | 131 ++++++++---------- 1 file changed, 61 insertions(+), 70 deletions(-) diff --git a/src/test/java/io/swagger/codegen/languages/kotlin/KotlinClientCodegenModelTest.java b/src/test/java/io/swagger/codegen/languages/kotlin/KotlinClientCodegenModelTest.java index 6fb424efb7..3f1a2db798 100644 --- a/src/test/java/io/swagger/codegen/languages/kotlin/KotlinClientCodegenModelTest.java +++ b/src/test/java/io/swagger/codegen/languages/kotlin/KotlinClientCodegenModelTest.java @@ -49,6 +49,7 @@ private Schema getArrayTestSchema() { private Schema getSimpleSchema() { return new Schema() + .type("object") .description("a sample model") .addProperties("id", new IntegerSchema() .format(SchemaTypeUtil.INTEGER64_FORMAT)) @@ -60,18 +61,8 @@ private Schema getSimpleSchema() { } private Schema getMapSchema() { - // return new ModelImpl() - // .description("a sample model") - // .property("mapping", new MapProperty() - // .additionalProperties(new StringProperty())) - // .required("id"); - - // final Schema propertySchema = new MapSchema().additionalProperties(new StringSchema()); - // return new Schema() - // .type("object") - // .description("a sample model") - // .addProperties("mapping", propertySchema); - return new Schema() + return new Schema() + .type("object") .description("a sample model") .addProperties("mapping", new MapSchema() .additionalProperties(new StringSchema())) @@ -81,12 +72,12 @@ private Schema getMapSchema() { private Schema getComplexSchema() { return new Schema() .description("a sample model") - .additionalProperties(new Schema().$ref("#/definitions/Child")); + .type("object") + .addProperties("child", new Schema().$ref("#/components/schemas/Child")); } @Test(description = "convert a simple model") - public void simpleModelTest() { - // final Model model = getSimpleModel(); + public void simpleModelTest() { final Schema schema = getSimpleSchema(); final KotlinClientCodegen codegen = new KotlinClientCodegen(); final CodegenModel cm = codegen.fromModel("sample", schema); @@ -236,60 +227,60 @@ public void mapPropertyTest() { Assert.assertTrue(getBooleanValue(property1, CodegenConstants.IS_PRIMITIVE_TYPE_EXT_NAME)); } - // @Test(description = "convert a model with complex property") - // public void complexPropertyTest() { - // final Schema schema = getComplexSchema(); - // final CodegenConfig codegen = new KotlinClientCodegen(); - // final CodegenModel cm = codegen.fromModel("sample", schema); - - // Assert.assertEquals(cm.name, "sample"); - // Assert.assertEquals(cm.classname, "Sample"); - // Assert.assertEquals(cm.description, "a sample model"); - // Assert.assertEquals(cm.vars.size(), 0); - - // // final CodegenProperty property1 = cm.vars.get(0); - // // Assert.assertEquals(property1.baseName, "child"); - // // Assert.assertEquals(property1.datatype, "Child"); - // // Assert.assertEquals(property1.name, "child"); - // // Assert.assertEquals(property1.baseType, "Child"); - // // Assert.assertFalse(property1.required); - // // Assert.assertTrue(getBooleanValue(property1, CodegenConstants.IS_NOT_CONTAINER_EXT_NAME)); - // } - - // @DataProvider(name = "modelNames") - // public static Object[][] modelNames() { - // return new Object[][]{ - // {"TestNs.TestClass", new ModelNameTest("TestNs.TestClass", "TestNsTestClass")}, - // {"$", new ModelNameTest("$", "Dollar")}, - // {"for", new ModelNameTest("`for`", "`for`")}, - // {"One Date: Wed, 28 Feb 2018 11:18:20 +1100 Subject: [PATCH 21/34] Cleaning up whitespace changes due to mustache/handlebars differences --- .../v2/kotlin-client/data_class.mustache | 16 ++++++++-------- .../kotlin-client/data_class_opt_var.mustache | 2 +- .../v2/kotlin-server/data_class.mustache | 18 +++++++++--------- .../kotlin-server/data_class_opt_var.mustache | 2 +- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/main/resources/v2/kotlin-client/data_class.mustache b/src/main/resources/v2/kotlin-client/data_class.mustache index 42b0bb8fe0..c7d1897367 100644 --- a/src/main/resources/v2/kotlin-client/data_class.mustache +++ b/src/main/resources/v2/kotlin-client/data_class.mustache @@ -1,15 +1,15 @@ /** * {{{description}}} -{{#vars}} +{{~#vars}} * @param {{name}} {{{description}}} -{{/vars}} +{{~/vars~}} */ data class {{classname}} ( -{{#requiredVars}} -{{>data_class_req_var}}{{^@last}}, -{{/@last}}{{/requiredVars}}{{#has this 'required'}}{{#has this 'optional'}}, -{{/has}}{{/has}}{{#optionalVars}}{{>data_class_opt_var}}{{^@last}}, -{{/@last}}{{/optionalVars}} +{{~#requiredVars~}} + {{>data_class_req_var}}{{^@last}},{{/@last}} +{{~/requiredVars~}} +{{#has this 'required'}}{{#has this 'optional'}},{{/has}}{{/has}} +{{~#optionalVars}}{{>data_class_opt_var}}{{^@last}},{{/@last}}{{/optionalVars}} ) { {{#has this 'enums'}}{{#vars}}{{#is this 'enum'}} /** @@ -22,4 +22,4 @@ data class {{classname}} ( {{/enumVars}}{{/allowableValues}} } {{/is}}{{/vars}}{{/has}} -} +} \ No newline at end of file diff --git a/src/main/resources/v2/kotlin-client/data_class_opt_var.mustache b/src/main/resources/v2/kotlin-client/data_class_opt_var.mustache index 7b1c72aa76..2d950f5bab 100644 --- a/src/main/resources/v2/kotlin-client/data_class_opt_var.mustache +++ b/src/main/resources/v2/kotlin-client/data_class_opt_var.mustache @@ -1,4 +1,4 @@ {{#description}} /* {{{description}}} */ -{{/description}} +{{~/description}} val {{{name}}}: {{#is this 'enum'}}{{classname}}.{{nameInCamelCase}}{{/is}}{{#isNot this 'enum'}}{{{datatype}}}{{/isNot}}? = {{#defaultvalue}}{{defaultvalue}}{{/defaultvalue}}{{^defaultvalue}}null{{/defaultvalue}} \ No newline at end of file diff --git a/src/main/resources/v2/kotlin-server/data_class.mustache b/src/main/resources/v2/kotlin-server/data_class.mustache index de71a9556a..c7d1897367 100644 --- a/src/main/resources/v2/kotlin-server/data_class.mustache +++ b/src/main/resources/v2/kotlin-server/data_class.mustache @@ -1,25 +1,25 @@ /** * {{{description}}} -{{#vars}} +{{~#vars}} * @param {{name}} {{{description}}} -{{/vars}} +{{~/vars~}} */ data class {{classname}} ( -{{#requiredVars}} -{{>data_class_req_var}}{{^@last}}, -{{/@last}}{{/requiredVars}}{{#has this 'required'}}{{#has this 'optional'}}, -{{/has}}{{/has}}{{#optionalVars}}{{>data_class_opt_var}}{{^@last}}, -{{/@last}}{{/optionalVars}} +{{~#requiredVars~}} + {{>data_class_req_var}}{{^@last}},{{/@last}} +{{~/requiredVars~}} +{{#has this 'required'}}{{#has this 'optional'}},{{/has}}{{/has}} +{{~#optionalVars}}{{>data_class_opt_var}}{{^@last}},{{/@last}}{{/optionalVars}} ) { {{#has this 'enums'}}{{#vars}}{{#is this 'enum'}} /** * {{{description}}} * Values: {{#allowableValues}}{{#enumVars}}{{&name}}{{^@last}},{{/@last}}{{/enumVars}}{{/allowableValues}} */ - enum class {{nameInCamelCase}}(val value: {{dataType}}){ + enum class {{nameInCamelCase}}(val value: {{datatype}}){ {{#allowableValues}}{{#enumVars}} {{&name}}({{{value}}}){{^@last}},{{/@last}}{{#@last}};{{/@last}} {{/enumVars}}{{/allowableValues}} } {{/is}}{{/vars}}{{/has}} -} +} \ No newline at end of file diff --git a/src/main/resources/v2/kotlin-server/data_class_opt_var.mustache b/src/main/resources/v2/kotlin-server/data_class_opt_var.mustache index 7b1c72aa76..2d950f5bab 100644 --- a/src/main/resources/v2/kotlin-server/data_class_opt_var.mustache +++ b/src/main/resources/v2/kotlin-server/data_class_opt_var.mustache @@ -1,4 +1,4 @@ {{#description}} /* {{{description}}} */ -{{/description}} +{{~/description}} val {{{name}}}: {{#is this 'enum'}}{{classname}}.{{nameInCamelCase}}{{/is}}{{#isNot this 'enum'}}{{{datatype}}}{{/isNot}}? = {{#defaultvalue}}{{defaultvalue}}{{/defaultvalue}}{{^defaultvalue}}null{{/defaultvalue}} \ No newline at end of file From 9c03ffe33a57b1398e5184a12edec5e657d86240 Mon Sep 17 00:00:00 2001 From: tcslater Date: Mon, 5 Mar 2018 15:50:20 +1100 Subject: [PATCH 22/34] Fix scope iteration issue from mustache->handlerbars migration --- .../v2/kotlin-server/libraries/ktor/Configuration.kt.mustache | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/v2/kotlin-server/libraries/ktor/Configuration.kt.mustache b/src/main/resources/v2/kotlin-server/libraries/ktor/Configuration.kt.mustache index db48170a28..b1262c872d 100644 --- a/src/main/resources/v2/kotlin-server/libraries/ktor/Configuration.kt.mustache +++ b/src/main/resources/v2/kotlin-server/libraries/ktor/Configuration.kt.mustache @@ -88,7 +88,7 @@ val ApplicationAuthProviders: Map = listOf Date: Mon, 5 Mar 2018 17:02:06 +1100 Subject: [PATCH 23/34] Fix null pointer in getSchemaType --- .../swagger/codegen/languages/kotlin/AbstractKotlinCodegen.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/io/swagger/codegen/languages/kotlin/AbstractKotlinCodegen.java b/src/main/java/io/swagger/codegen/languages/kotlin/AbstractKotlinCodegen.java index a766e78212..acfb9f5d0c 100644 --- a/src/main/java/io/swagger/codegen/languages/kotlin/AbstractKotlinCodegen.java +++ b/src/main/java/io/swagger/codegen/languages/kotlin/AbstractKotlinCodegen.java @@ -293,6 +293,7 @@ public String getSchemaType(Schema schema) { if (null == schemaType) { if (schema.getName() != null) { LOGGER.warn("No Type defined for Property " + schema.getName()); + return toModelName(schema.getName()); } else { return toModelName("kotlin.Any"); } From c3fc5a252f7ca186bfa6ecbbeb71a7250703f46f Mon Sep 17 00:00:00 2001 From: tcslater Date: Mon, 5 Mar 2018 17:03:23 +1100 Subject: [PATCH 24/34] Fix issues when multiple auth types are specified for an operation --- .../v2/kotlin-server/libraries/ktor/_api_body.mustache | 3 ++- .../v2/kotlin-server/libraries/ktor/_principal.mustache | 6 +++--- .../resources/v2/kotlin-server/libraries/ktor/api.mustache | 3 ++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/resources/v2/kotlin-server/libraries/ktor/_api_body.mustache b/src/main/resources/v2/kotlin-server/libraries/ktor/_api_body.mustache index 6ed8842742..68fdc1434f 100644 --- a/src/main/resources/v2/kotlin-server/libraries/ktor/_api_body.mustache +++ b/src/main/resources/v2/kotlin-server/libraries/ktor/_api_body.mustache @@ -1,6 +1,7 @@ {{#has this 'auth-methods'}} +val principals = mutableListOf() {{>libraries/ktor/_principal}} -if (principal == null) { +if (principals.any({ it != null })) { call.respond(HttpStatusCode.Unauthorized) } else { {{#examples}} diff --git a/src/main/resources/v2/kotlin-server/libraries/ktor/_principal.mustache b/src/main/resources/v2/kotlin-server/libraries/ktor/_principal.mustache index edb7808391..2b36b05d84 100644 --- a/src/main/resources/v2/kotlin-server/libraries/ktor/_principal.mustache +++ b/src/main/resources/v2/kotlin-server/libraries/ktor/_principal.mustache @@ -1,11 +1,11 @@ {{#authMethods}} {{#is this 'basic'}} -val principal = call.authentication.principal() + principals.add(call.authentication.principal()) {{/is}} {{#is this 'api-key'}} -val principal = call.authentication.principal() + principals.add(call.authentication.principal()) {{/is}} {{#is this 'oauth'}} -val principal = call.authentication.principal() + principals.add(call.authentication.principal()) {{/is}} {{/authMethods}} diff --git a/src/main/resources/v2/kotlin-server/libraries/ktor/api.mustache b/src/main/resources/v2/kotlin-server/libraries/ktor/api.mustache index d04ffb6db2..9dfad2103c 100644 --- a/src/main/resources/v2/kotlin-server/libraries/ktor/api.mustache +++ b/src/main/resources/v2/kotlin-server/libraries/ktor/api.mustache @@ -7,6 +7,7 @@ import io.ktor.auth.UserIdPrincipal import io.ktor.auth.authentication import io.ktor.auth.basicAuthentication import io.ktor.auth.oauth +import io.ktor.auth.Principal import io.ktor.auth.OAuthAccessTokenResponse import io.ktor.auth.OAuthServerSettings import io.ktor.http.ContentType @@ -73,7 +74,7 @@ fun Route.{{classname}}() { {{/is}} {{#is this 'api-key'}} // "Implement API key auth ({{{name}}}) for parameter name '{{{keyParamName}}}'." - apiKeyAuth("{{{keyParamName}}}", {{#isKeyInQuery}}"query"{{/isKeyInQuery}}{{#isKeyInHeader}}"header"{{/isKeyInHeader}}) { + apiKeyAuth("{{{keyParamName}}}", {{#is this 'key-in-query'}}"query"{{/is}}{{#is this 'key-in-header'}}"header"{{/is}}) { // TODO: "Verify key here , accessible as it.value" if (it.value == "keyboardcat") { ApiPrincipal(it) From ab9c61e2b8b9c2a197c17c40247cba9aa2b6746b Mon Sep 17 00:00:00 2001 From: tcslater Date: Mon, 5 Mar 2018 17:18:42 +1100 Subject: [PATCH 25/34] Update name of kotlin-client generator --- .../swagger/codegen/languages/kotlin/KotlinClientCodegen.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/swagger/codegen/languages/kotlin/KotlinClientCodegen.java b/src/main/java/io/swagger/codegen/languages/kotlin/KotlinClientCodegen.java index 27a68e53c6..f76340e198 100644 --- a/src/main/java/io/swagger/codegen/languages/kotlin/KotlinClientCodegen.java +++ b/src/main/java/io/swagger/codegen/languages/kotlin/KotlinClientCodegen.java @@ -65,7 +65,7 @@ public CodegenType getTag() { } public String getName() { - return "kotlin"; + return "kotlin-client"; } public String getHelp() { From 22a5cdf2227e5be439e33e657bc13d955b09eb70 Mon Sep 17 00:00:00 2001 From: tcslater Date: Tue, 6 Mar 2018 09:22:32 +1100 Subject: [PATCH 26/34] Implement getArgumentsLocation --- .../codegen/languages/kotlin/AbstractKotlinCodegen.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/io/swagger/codegen/languages/kotlin/AbstractKotlinCodegen.java b/src/main/java/io/swagger/codegen/languages/kotlin/AbstractKotlinCodegen.java index acfb9f5d0c..c68d4ab7e5 100644 --- a/src/main/java/io/swagger/codegen/languages/kotlin/AbstractKotlinCodegen.java +++ b/src/main/java/io/swagger/codegen/languages/kotlin/AbstractKotlinCodegen.java @@ -200,6 +200,11 @@ protected void addSwitch(String key, String description, Boolean defaultValue) { cliOptions.add(option); } + @Override + public String getArgumentsLocation() { + return ""; + } + @Override public String apiDocFileFolder() { return (outputFolder + "/" + apiDocPath).replace('/', File.separatorChar); From a8e1ff1f74c6f67f6dac50e3f4b0ffcc3be5063c Mon Sep 17 00:00:00 2001 From: tcslater Date: Wed, 7 Mar 2018 08:58:41 +1100 Subject: [PATCH 27/34] Remove guava dependency. Set default library option if not specified --- .../languages/kotlin/KotlinServerCodegen.java | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/main/java/io/swagger/codegen/languages/kotlin/KotlinServerCodegen.java b/src/main/java/io/swagger/codegen/languages/kotlin/KotlinServerCodegen.java index ba11bb3a36..f9ca912fd8 100644 --- a/src/main/java/io/swagger/codegen/languages/kotlin/KotlinServerCodegen.java +++ b/src/main/java/io/swagger/codegen/languages/kotlin/KotlinServerCodegen.java @@ -1,6 +1,5 @@ package io.swagger.codegen.languages.kotlin; -import com.google.common.collect.ImmutableMap; import io.swagger.codegen.CliOption; import io.swagger.codegen.CodegenConstants; import io.swagger.codegen.CodegenType; @@ -15,6 +14,8 @@ import com.github.jknack.handlebars.Handlebars; import org.apache.commons.lang3.StringUtils; +import static java.util.Collections.singletonMap; + public class KotlinServerCodegen extends AbstractKotlinCodegen { public static final String DEFAULT_LIBRARY = Constants.KTOR; @@ -26,15 +27,15 @@ public class KotlinServerCodegen extends AbstractKotlinCodegen { private Boolean compressionFeatureEnabled = true; // This is here to potentially warn the user when an option is not supoprted by the target framework. - private Map> optionsSupportedPerFramework = new ImmutableMap.Builder>() - .put(Constants.KTOR, Arrays.asList( - Constants.AUTOMATIC_HEAD_REQUESTS, - Constants.CONDITIONAL_HEADERS, - Constants.HSTS, - Constants.CORS, - Constants.COMPRESSION - )) - .build(); + private Map> optionsSupportedPerFramework = + singletonMap(Constants.KTOR, + Arrays.asList( + Constants.AUTOMATIC_HEAD_REQUESTS, + Constants.CONDITIONAL_HEADERS, + Constants.HSTS, + Constants.CORS, + Constants.COMPRESSION + )); /** * Constructs an instance of `KotlinServerCodegen`. @@ -132,6 +133,8 @@ public void processOpts() { if (additionalProperties.containsKey(CodegenConstants.LIBRARY)) { this.setLibrary((String) additionalProperties.get(CodegenConstants.LIBRARY)); + } else { + this.setLibrary(DEFAULT_LIBRARY); } if (additionalProperties.containsKey(Constants.AUTOMATIC_HEAD_REQUESTS)) { From 416e19961d44c7e732978037273e1ac82f909736 Mon Sep 17 00:00:00 2001 From: tcslater Date: Wed, 7 Mar 2018 10:31:30 +1100 Subject: [PATCH 28/34] Default generateApis option to true --- .../swagger/codegen/languages/kotlin/KotlinServerCodegen.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/io/swagger/codegen/languages/kotlin/KotlinServerCodegen.java b/src/main/java/io/swagger/codegen/languages/kotlin/KotlinServerCodegen.java index f9ca912fd8..542dfa0b06 100644 --- a/src/main/java/io/swagger/codegen/languages/kotlin/KotlinServerCodegen.java +++ b/src/main/java/io/swagger/codegen/languages/kotlin/KotlinServerCodegen.java @@ -131,6 +131,10 @@ public void processOpts() { embeddedTemplateDir = templateDir = String.format("%s/kotlin-server", DEFAULT_TEMPLATE_VERSION); } + if (!additionalProperties.containsKey("generateApis")) { + additionalProperties.put("generateApis", true); + } + if (additionalProperties.containsKey(CodegenConstants.LIBRARY)) { this.setLibrary((String) additionalProperties.get(CodegenConstants.LIBRARY)); } else { From af150714cf2f459a93e0b4a4c684cdafdbb68269 Mon Sep 17 00:00:00 2001 From: tcslater Date: Wed, 7 Mar 2018 12:09:15 +1100 Subject: [PATCH 29/34] Ensure that the parameter names in the path are valid kotlin names --- .../languages/kotlin/KotlinServerCodegen.java | 35 +++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/swagger/codegen/languages/kotlin/KotlinServerCodegen.java b/src/main/java/io/swagger/codegen/languages/kotlin/KotlinServerCodegen.java index 542dfa0b06..47de4a020e 100644 --- a/src/main/java/io/swagger/codegen/languages/kotlin/KotlinServerCodegen.java +++ b/src/main/java/io/swagger/codegen/languages/kotlin/KotlinServerCodegen.java @@ -1,5 +1,11 @@ package io.swagger.codegen.languages.kotlin; +import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.media.Schema; +import io.swagger.codegen.CodegenOperation; +import io.swagger.v3.oas.models.parameters.Parameter; + import io.swagger.codegen.CliOption; import io.swagger.codegen.CodegenConstants; import io.swagger.codegen.CodegenType; @@ -19,6 +25,8 @@ public class KotlinServerCodegen extends AbstractKotlinCodegen { public static final String DEFAULT_LIBRARY = Constants.KTOR; + public static final String GENERATE_APIS = "generateApis"; + static Logger LOGGER = LoggerFactory.getLogger(KotlinServerCodegen.class); private Boolean autoHeadFeatureEnabled = true; private Boolean conditionalHeadersFeatureEnabled = false; @@ -120,6 +128,23 @@ public CodegenType getTag() { return CodegenType.SERVER; } + @Override + public CodegenOperation fromOperation(String path, String httpMethod, Operation operation, Map schemas, OpenAPI openAPI) { + + // Ensure that the parameter names in the path are valid kotlin names + // they need to match the names in the generated data class, this is required by ktor Location + String modifiedPath = path; + if (operation.getParameters() != null) { + for (Parameter param : operation.getParameters()) { + String pathParamName = param.getName(); + String kotlinName = toVarName(pathParamName); + modifiedPath = modifiedPath.replace("{" + pathParamName + "}", "{" + kotlinName + "}"); + } + } + + return super.fromOperation(modifiedPath, httpMethod, operation, schemas, openAPI); + } + @Override public void processOpts() { super.processOpts(); @@ -131,8 +156,8 @@ public void processOpts() { embeddedTemplateDir = templateDir = String.format("%s/kotlin-server", DEFAULT_TEMPLATE_VERSION); } - if (!additionalProperties.containsKey("generateApis")) { - additionalProperties.put("generateApis", true); + if (!additionalProperties.containsKey(GENERATE_APIS)) { + additionalProperties.put(GENERATE_APIS, true); } if (additionalProperties.containsKey(CodegenConstants.LIBRARY)) { @@ -173,6 +198,8 @@ public void processOpts() { String packageFolder = (sourceFolder + File.separator + packageName).replace(".", File.separator); String resourcesFolder = "src/main/resources"; // not sure this can be user configurable. + + Boolean generateApis = additionalProperties.containsKey(GENERATE_APIS) && (Boolean)additionalProperties.get(GENERATE_APIS); supportingFiles.add(new SupportingFile("README.mustache", "", "README.md")); supportingFiles.add(new SupportingFile("Dockerfile.mustache", "", "Dockerfile")); @@ -184,7 +211,9 @@ public void processOpts() { supportingFiles.add(new SupportingFile("AppMain.kt.mustache", packageFolder, "AppMain.kt")); supportingFiles.add(new SupportingFile("Configuration.kt.mustache", packageFolder, "Configuration.kt")); - supportingFiles.add(new SupportingFile("Paths.kt.mustache", packageFolder, "Paths.kt")); + if (generateApis) { + supportingFiles.add(new SupportingFile("Paths.kt.mustache", packageFolder, "Paths.kt")); + } supportingFiles.add(new SupportingFile("application.conf.mustache", resourcesFolder, "application.conf")); supportingFiles.add(new SupportingFile("logback.xml", resourcesFolder, "logback.xml")); From 8397e8d9186a6e30d80c9e4fddf94cd9602958d4 Mon Sep 17 00:00:00 2001 From: Clarice Poh Date: Tue, 13 Mar 2018 11:21:49 +1100 Subject: [PATCH 30/34] Fixed Ktor Path template to generate nullable value for optional parameters as described in open-api definition --- .../resources/v2/kotlin-server/libraries/ktor/Paths.kt.mustache | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/v2/kotlin-server/libraries/ktor/Paths.kt.mustache b/src/main/resources/v2/kotlin-server/libraries/ktor/Paths.kt.mustache index 31ad8269d3..212c2245cd 100644 --- a/src/main/resources/v2/kotlin-server/libraries/ktor/Paths.kt.mustache +++ b/src/main/resources/v2/kotlin-server/libraries/ktor/Paths.kt.mustache @@ -33,7 +33,7 @@ object Paths { * {{#unescapedNotes}}{{.}}{{/unescapedNotes}} {{#allParams}}* @param {{paramName}} {{description}} {{^required}}(optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}} {{/allParams}}*/ - @Location("{{path}}") class {{operationId}}({{#allParams}}val {{paramName}}: {{{dataType}}}{{#has this 'more'}}, {{/has}}{{/allParams}}) + @Location("{{path}}") class {{operationId}}({{#allParams}}val {{paramName}}: {{{dataType}}}{{^required}}? = null{{/required}}{{#has this 'more'}}, {{/has}}{{/allParams}}) {{/bodyAllowed}} {{/operation}} From 2ec6d6536075b40debdffcc8fb3fa39fecae7574 Mon Sep 17 00:00:00 2001 From: tcslater Date: Tue, 13 Mar 2018 13:34:33 +1100 Subject: [PATCH 31/34] Add .vscode to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index c9d68cad87..f8d6516c04 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ # Visual Studio Code .project +.vscode # Mobile Tools for Java (J2ME) .mtj.tmp/ From 899d97e8986cea3f5b7c6ce87b484846668850ec Mon Sep 17 00:00:00 2001 From: tcslater Date: Fri, 16 Mar 2018 08:23:25 +1100 Subject: [PATCH 32/34] Made all LOGGERs private static. Removed duplicated enumPropertyNaming declaration in child class --- .../codegen/languages/kotlin/AbstractKotlinCodegen.java | 3 +-- .../codegen/languages/kotlin/KotlinClientCodegen.java | 5 ++--- .../codegen/languages/kotlin/KotlinServerCodegen.java | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/main/java/io/swagger/codegen/languages/kotlin/AbstractKotlinCodegen.java b/src/main/java/io/swagger/codegen/languages/kotlin/AbstractKotlinCodegen.java index c68d4ab7e5..7b2ffa8a4a 100644 --- a/src/main/java/io/swagger/codegen/languages/kotlin/AbstractKotlinCodegen.java +++ b/src/main/java/io/swagger/codegen/languages/kotlin/AbstractKotlinCodegen.java @@ -18,8 +18,7 @@ import java.util.Map; public abstract class AbstractKotlinCodegen extends DefaultCodegenConfig { - static Logger LOGGER = LoggerFactory.getLogger(AbstractKotlinCodegen.class); - + private static Logger LOGGER = LoggerFactory.getLogger(AbstractKotlinCodegen.class); protected String artifactId; protected String artifactVersion = "1.0.0"; diff --git a/src/main/java/io/swagger/codegen/languages/kotlin/KotlinClientCodegen.java b/src/main/java/io/swagger/codegen/languages/kotlin/KotlinClientCodegen.java index f76340e198..11a640a1ce 100644 --- a/src/main/java/io/swagger/codegen/languages/kotlin/KotlinClientCodegen.java +++ b/src/main/java/io/swagger/codegen/languages/kotlin/KotlinClientCodegen.java @@ -15,9 +15,8 @@ public class KotlinClientCodegen extends AbstractKotlinCodegen { - public static final String DATE_LIBRARY = "dateLibrary"; - protected CodegenConstants.ENUM_PROPERTY_NAMING_TYPE enumPropertyNaming = CodegenConstants.ENUM_PROPERTY_NAMING_TYPE.camelCase; - static Logger LOGGER = LoggerFactory.getLogger(KotlinClientCodegen.class); + public static final String DATE_LIBRARY = "dateLibrary"; + private static Logger LOGGER = LoggerFactory.getLogger(KotlinClientCodegen.class); protected String dateLibrary = DateLibrary.JAVA8.value; diff --git a/src/main/java/io/swagger/codegen/languages/kotlin/KotlinServerCodegen.java b/src/main/java/io/swagger/codegen/languages/kotlin/KotlinServerCodegen.java index 47de4a020e..dc5c228c9d 100644 --- a/src/main/java/io/swagger/codegen/languages/kotlin/KotlinServerCodegen.java +++ b/src/main/java/io/swagger/codegen/languages/kotlin/KotlinServerCodegen.java @@ -27,7 +27,7 @@ public class KotlinServerCodegen extends AbstractKotlinCodegen { public static final String DEFAULT_LIBRARY = Constants.KTOR; public static final String GENERATE_APIS = "generateApis"; - static Logger LOGGER = LoggerFactory.getLogger(KotlinServerCodegen.class); + private static Logger LOGGER = LoggerFactory.getLogger(KotlinServerCodegen.class); private Boolean autoHeadFeatureEnabled = true; private Boolean conditionalHeadersFeatureEnabled = false; private Boolean hstsFeatureEnabled = true; From c18a0ceaab2ff3c0fd56bc8b2a66b40b28fcb181 Mon Sep 17 00:00:00 2001 From: tcslater Date: Mon, 19 Mar 2018 22:06:45 +1100 Subject: [PATCH 33/34] update ktor version to 0.9.1 --- .../v2/kotlin-server/libraries/ktor/build.gradle.mustache | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/v2/kotlin-server/libraries/ktor/build.gradle.mustache b/src/main/resources/v2/kotlin-server/libraries/ktor/build.gradle.mustache index 494ea0e76f..f053cf001f 100644 --- a/src/main/resources/v2/kotlin-server/libraries/ktor/build.gradle.mustache +++ b/src/main/resources/v2/kotlin-server/libraries/ktor/build.gradle.mustache @@ -8,7 +8,7 @@ task wrapper(type: Wrapper) { buildscript { ext.kotlin_version = '1.2.10' - ext.ktor_version = '0.9.1-alpha-9' + ext.ktor_version = '0.9.1' ext.shadow_version = '2.0.2' repositories { From 0d502dbc1e4b7e0ec595dd726917366ffeee2136 Mon Sep 17 00:00:00 2001 From: Clarice Poh Date: Fri, 6 Apr 2018 14:54:22 +1000 Subject: [PATCH 34/34] SWCRS-83: Fixed Kotlin Generator to resolve typealiases compilation error for Array Schema --- .../kotlin/AbstractKotlinCodegen.java | 33 ++++++++++++------- .../languages/kotlin/KotlinServerCodegen.java | 30 ++++++++++++----- .../resources/v2/kotlin-server/model.mustache | 2 +- 3 files changed, 45 insertions(+), 20 deletions(-) diff --git a/src/main/java/io/swagger/codegen/languages/kotlin/AbstractKotlinCodegen.java b/src/main/java/io/swagger/codegen/languages/kotlin/AbstractKotlinCodegen.java index c68d4ab7e5..c58e4c830c 100644 --- a/src/main/java/io/swagger/codegen/languages/kotlin/AbstractKotlinCodegen.java +++ b/src/main/java/io/swagger/codegen/languages/kotlin/AbstractKotlinCodegen.java @@ -16,10 +16,12 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import java.util.Set; public abstract class AbstractKotlinCodegen extends DefaultCodegenConfig { static Logger LOGGER = LoggerFactory.getLogger(AbstractKotlinCodegen.class); + private Set instantiationLibraryFunction; protected String artifactId; protected String artifactVersion = "1.0.0"; @@ -48,7 +50,7 @@ public AbstractKotlinCodegen() { "kotlin.Boolean", "kotlin.Char", "kotlin.String", - "kotlin.Array", + "kotlin.Array", "kotlin.collections.List", "kotlin.collections.Map", "kotlin.collections.Set" @@ -137,6 +139,11 @@ public AbstractKotlinCodegen() { "kotlin.collections.Map" )); + instantiationLibraryFunction = new HashSet(Arrays.asList( + "arrayOf", + "mapOf" + )); + typeMapping = new HashMap(); typeMapping.put("string", "kotlin.String"); typeMapping.put("boolean", "kotlin.Boolean"); @@ -251,7 +258,7 @@ public void setEnumPropertyNaming(final String enumPropertyNamingType) { } throw new RuntimeException(sb.toString()); } - } + } /** * Output the type declaration of the property @@ -271,8 +278,8 @@ public String getTypeDeclaration(Schema propertySchema) { return null; } // Maps will be keyed only by primitive Kotlin string - return String.format("%s", getSchemaType(propertySchema), getTypeDeclaration(inner)); - } + return String.format("%s", getSchemaType(propertySchema), getTypeDeclaration(inner)); + } return super.getTypeDeclaration(propertySchema); } @@ -288,8 +295,6 @@ public String getAlias(String name) { public String getSchemaType(Schema schema) { String schemaType = super.getSchemaType(schema); - schemaType = getAlias(schemaType); - // don't apply renaming on types from the typeMapping if (typeMapping.containsKey(schemaType)) { return toModelName(typeMapping.get(schemaType)); @@ -302,7 +307,7 @@ public String getSchemaType(Schema schema) { } else { return toModelName("kotlin.Any"); } - } + } return toModelName(schemaType); } @@ -497,9 +502,9 @@ public String toModelName(final String name) { @Override public String toVarName(String name) { - return super.toVarName(sanitizeKotlinSpecificNames(name)); + return super.toVarName(sanitizeKotlinSpecificNames(name)); } - + /** * Provides a strongly typed declaration for simple arrays of some type and arrays of arrays of some type. * @@ -516,7 +521,7 @@ private String getArrayTypeDeclaration(ArraySchema arraySchema) { // TODO: We may want to differentiate here between generics and primitive arrays. instantiationType.append("<").append(nestedType).append(">"); return instantiationType.toString(); - } + } /** * Sanitize against Kotlin specific naming conventions, which may differ from those required by {@link DefaultCodegen#sanitizeName}. @@ -567,7 +572,13 @@ protected boolean isReservedWord(String word) { @Override protected boolean needToImport(String type) { // provides extra protection against improperly trying to import language primitives and java types - boolean imports = !type.startsWith("kotlin.") && !type.startsWith("java.") && !defaultIncludes.contains(type) && !languageSpecificPrimitives.contains(type); + boolean imports = + !type.startsWith("kotlin.") && + !type.startsWith("java.") && + !defaultIncludes.contains(type) && + !languageSpecificPrimitives.contains(type) && + !instantiationLibraryFunction.contains(type); + return imports; } } diff --git a/src/main/java/io/swagger/codegen/languages/kotlin/KotlinServerCodegen.java b/src/main/java/io/swagger/codegen/languages/kotlin/KotlinServerCodegen.java index 47de4a020e..5cf42a4530 100644 --- a/src/main/java/io/swagger/codegen/languages/kotlin/KotlinServerCodegen.java +++ b/src/main/java/io/swagger/codegen/languages/kotlin/KotlinServerCodegen.java @@ -3,12 +3,15 @@ import io.swagger.v3.oas.models.Operation; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.media.Schema; -import io.swagger.codegen.CodegenOperation; +import io.swagger.v3.oas.models.media.ArraySchema; import io.swagger.v3.oas.models.parameters.Parameter; import io.swagger.codegen.CliOption; import io.swagger.codegen.CodegenConstants; import io.swagger.codegen.CodegenType; +import io.swagger.codegen.CodegenModel; +import io.swagger.codegen.CodegenProperty; +import io.swagger.codegen.CodegenOperation; import io.swagger.codegen.SupportingFile; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,15 +38,15 @@ public class KotlinServerCodegen extends AbstractKotlinCodegen { private Boolean compressionFeatureEnabled = true; // This is here to potentially warn the user when an option is not supoprted by the target framework. - private Map> optionsSupportedPerFramework = - singletonMap(Constants.KTOR, + private Map> optionsSupportedPerFramework = + singletonMap(Constants.KTOR, Arrays.asList( Constants.AUTOMATIC_HEAD_REQUESTS, Constants.CONDITIONAL_HEADERS, Constants.HSTS, Constants.CORS, Constants.COMPRESSION - )); + )); /** * Constructs an instance of `KotlinServerCodegen`. @@ -128,9 +131,21 @@ public CodegenType getTag() { return CodegenType.SERVER; } + /** + * Handle typealias for schema of Array type + */ + @Override + public CodegenModel fromModel(String name, Schema schema, Map allDefinitions) { + CodegenModel codegenModel = super.fromModel(name, schema, allDefinitions); + + if (schema instanceof ArraySchema) { + codegenModel.dataType = getTypeDeclaration(schema); + } + return codegenModel; + } + @Override public CodegenOperation fromOperation(String path, String httpMethod, Operation operation, Map schemas, OpenAPI openAPI) { - // Ensure that the parameter names in the path are valid kotlin names // they need to match the names in the generated data class, this is required by ktor Location String modifiedPath = path; @@ -198,7 +213,6 @@ public void processOpts() { String packageFolder = (sourceFolder + File.separator + packageName).replace(".", File.separator); String resourcesFolder = "src/main/resources"; // not sure this can be user configurable. - Boolean generateApis = additionalProperties.containsKey(GENERATE_APIS) && (Boolean)additionalProperties.get(GENERATE_APIS); supportingFiles.add(new SupportingFile("README.mustache", "", "README.md")); @@ -210,9 +224,9 @@ public void processOpts() { supportingFiles.add(new SupportingFile("AppMain.kt.mustache", packageFolder, "AppMain.kt")); supportingFiles.add(new SupportingFile("Configuration.kt.mustache", packageFolder, "Configuration.kt")); - + if (generateApis) { - supportingFiles.add(new SupportingFile("Paths.kt.mustache", packageFolder, "Paths.kt")); + supportingFiles.add(new SupportingFile("Paths.kt.mustache", packageFolder, "Paths.kt")); } supportingFiles.add(new SupportingFile("application.conf.mustache", resourcesFolder, "application.conf")); diff --git a/src/main/resources/v2/kotlin-server/model.mustache b/src/main/resources/v2/kotlin-server/model.mustache index 852d62cdbd..6f0f576000 100644 --- a/src/main/resources/v2/kotlin-server/model.mustache +++ b/src/main/resources/v2/kotlin-server/model.mustache @@ -9,6 +9,6 @@ import org.threeten.bp.LocalDateTime {{#models}} {{#model}} -{{#is this 'alias'}}typealias {{classname}} = {{dataType}}{{/is}}{{#isNot this 'alias'}}{{#is this 'enum'}}{{>enum_class}}{{/is}}{{#isNot this 'enum'}}{{>data_class}}{{/isNot}}{{/isNot}} +{{#is this 'alias'}}typealias {{classname}} = {{{dataType}}}{{/is}}{{#isNot this 'alias'}}{{#is this 'enum'}}{{>enum_class}}{{/is}}{{#isNot this 'enum'}}{{>data_class}}{{/isNot}}{{/isNot}} {{/model}} {{/models}}