forked from loafle/openapi-generator-original
add kotlin client, server generator
This commit is contained in:
@@ -0,0 +1,534 @@
|
||||
package org.openapitools.codegen.languages;
|
||||
|
||||
import org.openapitools.codegen.CliOption;
|
||||
import org.openapitools.codegen.CodegenConfig;
|
||||
import org.openapitools.codegen.CodegenConstants;
|
||||
import org.openapitools.codegen.DefaultCodegen;
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.Operation;
|
||||
import io.swagger.v3.oas.models.media.*;
|
||||
import io.swagger.v3.oas.models.responses.ApiResponse;
|
||||
|
||||
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 DefaultCodegen implements CodegenConfig {
|
||||
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<String>(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<String>(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<String>(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<String, String>();
|
||||
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<kotlin.Byte>");
|
||||
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<String, String>();
|
||||
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()));
|
||||
}
|
||||
|
||||
@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 getSchemaType(Schema p) {
|
||||
String openAPIType = super.getSchemaType(p);
|
||||
String type;
|
||||
// This maps, for example, long -> kotlin.Long based on hashes in this type's constructor
|
||||
if (typeMapping.containsKey(openAPIType)) {
|
||||
type = typeMapping.get(openAPIType);
|
||||
if (languageSpecificPrimitives.contains(type)) {
|
||||
return toModelName(type);
|
||||
}
|
||||
} else {
|
||||
type = openAPIType;
|
||||
}
|
||||
return toModelName(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the type declaration of the property
|
||||
*
|
||||
* @param p Swagger Property object
|
||||
* @return a string presentation of the property type
|
||||
*/
|
||||
@Override
|
||||
public String getTypeDeclaration(Schema p) {
|
||||
if (p instanceof ArraySchema) {
|
||||
return getArrayTypeDeclaration((ArraySchema) p);
|
||||
} else if (p instanceof MapSchema) {
|
||||
MapSchema mp = (MapSchema) p;
|
||||
Schema inner = (Schema) mp.getAdditionalProperties();
|
||||
|
||||
// Maps will be keyed only by primitive Kotlin string
|
||||
return getSchemaType(p) + "<kotlin.String, " + getTypeDeclaration(inner) + ">";
|
||||
}
|
||||
return super.getTypeDeclaration(p);
|
||||
}
|
||||
|
||||
@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<String, Object> postProcessModels(Map<String, Object> 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 Array schema
|
||||
* @return type declaration of array
|
||||
*/
|
||||
private String getArrayTypeDeclaration(ArraySchema arr) {
|
||||
// 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 = arr.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<String, String> 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package org.openapitools.codegen.languages;
|
||||
|
||||
import org.openapitools.codegen.CliOption;
|
||||
import org.openapitools.codegen.CodegenConstants;
|
||||
import org.openapitools.codegen.CodegenType;
|
||||
import org.openapitools.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<String, String> 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"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
package org.openapitools.codegen.languages;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.samskivert.mustache.Mustache;
|
||||
import org.openapitools.codegen.CliOption;
|
||||
import org.openapitools.codegen.CodegenConstants;
|
||||
import org.openapitools.codegen.CodegenType;
|
||||
import org.openapitools.codegen.SupportingFile;
|
||||
import org.openapitools.codegen.mustache.*;
|
||||
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.Operation;
|
||||
import io.swagger.v3.oas.models.media.*;
|
||||
import io.swagger.v3.oas.models.responses.ApiResponse;
|
||||
|
||||
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<String, List<String>> optionsSupportedPerFramework = new ImmutableMap.Builder<String, List<String>>()
|
||||
.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<String, Object> objs) {
|
||||
|
||||
Map<String, Mustache.Lambda> lambdas = new ImmutableMap.Builder<String, Mustache.Lambda>()
|
||||
.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.";
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,8 @@ org.openapitools.codegen.languages.ConfluenceWikiCodegen
|
||||
org.openapitools.codegen.languages.CppRestClientCodegen
|
||||
org.openapitools.codegen.languages.DartClientCodegen
|
||||
org.openapitools.codegen.languages.ElixirClientCodegen
|
||||
org.openapitools.codegen.languages.KotlinClientCodegen
|
||||
org.openapitools.codegen.languages.KotlinServerCodegen
|
||||
org.openapitools.codegen.languages.HaskellServantCodegen
|
||||
org.openapitools.codegen.languages.LumenServerCodegen
|
||||
org.openapitools.codegen.languages.ObjcClientCodegen
|
||||
|
||||
Reference in New Issue
Block a user