From aa5a7ad54021487ebdcdf84e04666b16ff3f8111 Mon Sep 17 00:00:00 2001 From: wing328 Date: Tue, 27 Mar 2018 22:45:21 +0800 Subject: [PATCH] add scala client generators --- .../languages/AbstractScalaCodegen.java | 277 +++++++++++++++ .../languages/AkkaScalaClientCodegen.java | 336 ++++++++++++++++++ .../codegen/languages/ScalaClientCodegen.java | 240 +++++++++++++ .../org.openapitools.codegen.CodegenConfig | 2 + 4 files changed, 855 insertions(+) create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractScalaCodegen.java create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AkkaScalaClientCodegen.java create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaClientCodegen.java diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractScalaCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractScalaCodegen.java new file mode 100644 index 00000000000..ae1a2532c64 --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractScalaCodegen.java @@ -0,0 +1,277 @@ +package org.openapitools.codegen.languages; + +import java.io.File; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import com.samskivert.mustache.Escapers; +import com.samskivert.mustache.Mustache; +import org.openapitools.codegen.CliOption; +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 org.apache.commons.lang3.StringUtils; + +public abstract class AbstractScalaCodegen extends DefaultCodegen { + + protected String modelPropertyNaming = "camelCase"; + protected String invokerPackage = "io.swagger.client"; + protected String sourceFolder = "src/main/scala"; + protected boolean stripPackageName = true; + + public AbstractScalaCodegen() { + super(); + + languageSpecificPrimitives.addAll(Arrays.asList( + "String", + "boolean", + "Boolean", + "Double", + "Int", + "Long", + "Float", + "Object", + "Any", + "List", + "Seq", + "Map", + "Array")); + + reservedWords.addAll(Arrays.asList( + "abstract", + "case", + "catch", + "class", + "def", + "do", + "else", + "extends", + "false", + "final", + "finally", + "for", + "forSome", + "if", + "implicit", + "import", + "lazy", + "match", + "new", + "null", + "object", + "override", + "package", + "private", + "protected", + "return", + "sealed", + "super", + "this", + "throw", + "trait", + "try", + "true", + "type", + "val", + "var", + "while", + "with", + "yield" + )); + + cliOptions.add(new CliOption(CodegenConstants.MODEL_PACKAGE, CodegenConstants.MODEL_PACKAGE_DESC)); + cliOptions.add(new CliOption(CodegenConstants.API_PACKAGE, CodegenConstants.API_PACKAGE_DESC)); + cliOptions.add(new CliOption(CodegenConstants.SOURCE_FOLDER, CodegenConstants.SOURCE_FOLDER_DESC)); + } + + @Override + public void processOpts() { + super.processOpts(); + + if (additionalProperties.containsKey(CodegenConstants.SOURCE_FOLDER)) { + this.setSourceFolder((String) additionalProperties.get(CodegenConstants.SOURCE_FOLDER)); + } + if (additionalProperties.containsKey(CodegenConstants.STRIP_PACKAGE_NAME) && + "false".equalsIgnoreCase(additionalProperties.get(CodegenConstants.STRIP_PACKAGE_NAME).toString())) { + this.stripPackageName = false; + additionalProperties.put(CodegenConstants.STRIP_PACKAGE_NAME, false); + LOGGER.warn("stripPackageName=false. Compilation errors may occur if API type names clash with types " + + "in the default imports"); + } + } + + public void setSourceFolder(String sourceFolder) { + this.sourceFolder = sourceFolder; + } + + public String getSourceFolder() { + return sourceFolder; + } + + @Override + public String escapeReservedWord(String name) { + if (this.reservedWordsMappings().containsKey(name)) { + return this.reservedWordsMappings().get(name); + } + // Reserved words will be further escaped at the mustache compiler level. + // Scala escaping done here (via `, without compiler escaping) would otherwise be HTML encoded. + return "`" + name + "`"; + } + + @Override + public Mustache.Compiler processCompiler(Mustache.Compiler compiler) { + Mustache.Escaper SCALA = new Mustache.Escaper() { + @Override public String escape (String text) { + // Fix included as suggested by akkie in #6393 + // The given text is a reserved word which is escaped by enclosing it with grave accents. If we would + // escape that with the default Mustache `HTML` escaper, then the escaper would also escape our grave + // accents. So we remove the grave accents before the escaping and add it back after the escaping. + if (text.startsWith("`") && text.endsWith("`")) { + String unescaped = text.substring(1, text.length() - 1); + return "`" + Escapers.HTML.escape(unescaped) + "`"; + } + + // All none reserved words will be escaped with the default Mustache `HTML` escaper + return Escapers.HTML.escape(text); + } + }; + + return compiler.withEscaper(SCALA); + } + + @Override + public String apiFileFolder() { + return outputFolder + "/" + sourceFolder + "/" + apiPackage().replace('.', File.separatorChar); + } + + @Override + public String modelFileFolder() { + return outputFolder + "/" + sourceFolder + "/" + modelPackage().replace('.', File.separatorChar); + } + + @Override + public String getTypeDeclaration(Schema p) { + if (p instanceof ArraySchema) { + ArraySchema ap = (ArraySchema) p; + Schema inner = ap.getItems(); + return getSchemaType(p) + "[" + getTypeDeclaration(inner) + "]"; + } else if (p instanceof MapSchema) { + MapSchema mp = (MapSchema) p; + Schema inner = (Schema) mp.getAdditionalProperties(); + + return getSchemaType(p) + "[String, " + getTypeDeclaration(inner) + "]"; + } + return super.getTypeDeclaration(p); + } + + @Override + public String getSchemaType(Schema p) { + String swaggerType = super.getSchemaType(p); + String type = null; + if (typeMapping.containsKey(swaggerType)) { + type = typeMapping.get(swaggerType); + if (languageSpecificPrimitives.contains(type)) { + return toModelName(type); + } + } else { + type = swaggerType; + } + return toModelName(type); + } + + @Override + public String toInstantiationType(Schema p) { + if (p instanceof MapSchema) { + MapSchema ap = (MapSchema) p; + String inner = getSchemaType((Schema)ap.getAdditionalProperties()); + return instantiationTypes.get("map") + "[String, " + inner + "]"; + } else if (p instanceof ArraySchema) { + ArraySchema ap = (ArraySchema) p; + String inner = getSchemaType(ap.getItems()); + return instantiationTypes.get("array") + "[" + inner + "]"; + } else { + return null; + } + } + + @Override + public String toDefaultValue(Schema p) { + if (p instanceof StringSchema) { + return "null"; + } else if (p instanceof BooleanSchema) { + return "null"; + } else if (p instanceof DateSchema) { + return "null"; + } else if (p instanceof DateTimeSchema) { + return "null"; + } else if (p instanceof NumberSchema) { + return "null"; + } else if (p instanceof IntegerSchema) { + return "null"; + } else if (p instanceof MapSchema) { + MapSchema ap = (MapSchema) p; + String inner = getSchemaType((Schema) ap.getAdditionalProperties()); + return "new HashMap[String, " + inner + "]() "; + } else if (p instanceof ArraySchema) { + ArraySchema ap = (ArraySchema) p; + String inner = getSchemaType(ap.getItems()); + return "new ListBuffer[" + inner + "]() "; + } else { + return "null"; + } + } + + @Override + public Map postProcessModels(Map objs) { + // remove model imports to avoid warnings for importing class in the same package in Scala + List> imports = (List>) objs.get("imports"); + final String prefix = modelPackage() + "."; + Iterator> iterator = imports.iterator(); + while (iterator.hasNext()) { + String _import = iterator.next().get("import"); + if (_import.startsWith(prefix)) iterator.remove(); + } + return objs; + } + + @Override + public String toModelFilename(String name) { + // should be the same as the model name + return toModelName(name); + } + + @Override + public String escapeUnsafeCharacters(String input) { + return input.replace("*/", "*_/").replace("/*", "/_*"); + } + + protected String formatIdentifier(String name, boolean capitalized) { + String identifier = camelize(sanitizeName(name), true); + if (capitalized) { + identifier = StringUtils.capitalize(identifier); + } + if (identifier.matches("[a-zA-Z_$][\\w_$]+") && !isReservedWord(identifier)) { + return identifier; + } + return escapeReservedWord(identifier); + } + + protected String stripPackageName(String input) { + if (!stripPackageName || StringUtils.isEmpty(input) || input.lastIndexOf(".") < 0) + return input; + + int lastIndexOfDot = input.lastIndexOf("."); + return input.substring(lastIndexOfDot + 1); + } + + @Override + public String escapeQuotationMark(String input) { + // remove " to avoid code injection + return input.replace("\"", ""); + } +} diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AkkaScalaClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AkkaScalaClientCodegen.java new file mode 100644 index 00000000000..a70aa9f4e8f --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AkkaScalaClientCodegen.java @@ -0,0 +1,336 @@ +package org.openapitools.codegen.languages; + +import com.google.common.base.CaseFormat; +import com.samskivert.mustache.Mustache; +import com.samskivert.mustache.Template; + +import io.swagger.v3.oas.models.security.SecurityScheme; +import org.openapitools.codegen.CliOption; +import org.openapitools.codegen.CodegenConfig; +import org.openapitools.codegen.CodegenConstants; +import org.openapitools.codegen.CodegenOperation; +import org.openapitools.codegen.CodegenProperty; +import org.openapitools.codegen.CodegenResponse; +import org.openapitools.codegen.CodegenSecurity; +import org.openapitools.codegen.CodegenType; +import org.openapitools.codegen.SupportingFile; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.media.*; + + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +public class AkkaScalaClientCodegen extends AbstractScalaCodegen implements CodegenConfig { + protected String mainPackage = "io.swagger.client"; + protected String groupId = "io.swagger"; + protected String artifactId = "swagger-client"; + protected String artifactVersion = "1.0.0"; + protected String resourcesFolder = "src/main/resources"; + protected String configKey = "apiRequest"; + protected int defaultTimeoutInMs = 5000; + protected String configKeyPath = mainPackage; + protected boolean registerNonStandardStatusCodes = true; + protected boolean renderJavadoc = true; + protected boolean removeOAuthSecurities = true; + /** + * If set to true, only the default response (the one with le lowest 2XX code) will be considered as a success, and all + * others as ApiErrors. + * If set to false, all responses defined in the model will be considered as a success upon reception. Only http errors, + * unmarshalling problems and any other RuntimeException will be considered as ApiErrors. + */ + protected boolean onlyOneSuccess = true; + + @SuppressWarnings("hiding") + protected Logger LOGGER = LoggerFactory.getLogger(AkkaScalaClientCodegen.class); + + public AkkaScalaClientCodegen() { + super(); + outputFolder = "generated-code/scala"; + modelTemplateFiles.put("model.mustache", ".scala"); + apiTemplateFiles.put("api.mustache", ".scala"); + embeddedTemplateDir = templateDir = "akka-scala"; + apiPackage = mainPackage + ".api"; + modelPackage = mainPackage + ".model"; + invokerPackage = mainPackage + ".core"; + + setReservedWordsLowerCase( + Arrays.asList( + "abstract", "case", "catch", "class", "def", "do", "else", "extends", + "false", "final", "finally", "for", "forSome", "if", "implicit", + "import", "lazy", "match", "new", "null", "object", "override", "package", + "private", "protected", "return", "sealed", "super", "this", "throw", + "trait", "try", "true", "type", "val", "var", "while", "with", "yield") + ); + + additionalProperties.put(CodegenConstants.INVOKER_PACKAGE, invokerPackage); + additionalProperties.put(CodegenConstants.GROUP_ID, groupId); + additionalProperties.put(CodegenConstants.ARTIFACT_ID, artifactId); + additionalProperties.put(CodegenConstants.ARTIFACT_VERSION, artifactVersion); + additionalProperties.put("configKey", configKey); + additionalProperties.put("configKeyPath", configKeyPath); + additionalProperties.put("defaultTimeout", defaultTimeoutInMs); + if (renderJavadoc) { + additionalProperties.put("javadocRenderer", new JavadocLambda()); + } + additionalProperties.put("fnCapitalize", new CapitalizeLambda()); + additionalProperties.put("fnCamelize", new CamelizeLambda(false)); + additionalProperties.put("fnEnumEntry", new EnumEntryLambda()); + additionalProperties.put("onlyOneSuccess", onlyOneSuccess); + + supportingFiles.add(new SupportingFile("README.mustache", "", "README.md")); + supportingFiles.add(new SupportingFile("pom.mustache", "", "pom.xml")); + supportingFiles.add(new SupportingFile("build.sbt.mustache", "", "build.sbt")); + supportingFiles.add(new SupportingFile("reference.mustache", resourcesFolder, "reference.conf")); + final String invokerFolder = (sourceFolder + File.separator + invokerPackage).replace(".", File.separator); + supportingFiles.add(new SupportingFile("apiRequest.mustache", invokerFolder, "ApiRequest.scala")); + supportingFiles.add(new SupportingFile("apiInvoker.mustache", invokerFolder, "ApiInvoker.scala")); + supportingFiles.add(new SupportingFile("requests.mustache", invokerFolder, "requests.scala")); + supportingFiles.add(new SupportingFile("apiSettings.mustache", invokerFolder, "ApiSettings.scala")); + final String apiFolder = (sourceFolder + File.separator + apiPackage).replace(".", File.separator); + supportingFiles.add(new SupportingFile("enumsSerializers.mustache", apiFolder, "EnumsSerializers.scala")); + + importMapping.remove("Seq"); + importMapping.remove("List"); + importMapping.remove("Set"); + importMapping.remove("Map"); + + importMapping.put("DateTime", "org.joda.time.DateTime"); + + typeMapping = new HashMap(); + typeMapping.put("array", "Seq"); + typeMapping.put("set", "Set"); + typeMapping.put("boolean", "Boolean"); + typeMapping.put("string", "String"); + typeMapping.put("int", "Int"); + typeMapping.put("integer", "Int"); + typeMapping.put("long", "Long"); + typeMapping.put("float", "Float"); + typeMapping.put("byte", "Byte"); + typeMapping.put("short", "Short"); + typeMapping.put("char", "Char"); + typeMapping.put("long", "Long"); + typeMapping.put("double", "Double"); + typeMapping.put("object", "Any"); + typeMapping.put("file", "File"); + typeMapping.put("number", "Double"); + + instantiationTypes.put("array", "ListBuffer"); + instantiationTypes.put("map", "Map"); + } + + @Override + public CodegenType getTag() { + return CodegenType.CLIENT; + } + + @Override + public String getName() { + return "akka-scala"; + } + + @Override + public String getHelp() { + return "Generates a Scala client library (beta) base on Akka/Spray."; + } + + @Override + public String escapeReservedWord(String name) { + if(this.reservedWordsMappings().containsKey(name)) { + return this.reservedWordsMappings().get(name); + } + return "`" + name + "`"; + } + + @Override + public Map postProcessOperations(Map objs) { + if (registerNonStandardStatusCodes) { + try { + @SuppressWarnings("unchecked") + Map> opsMap = (Map>) objs.get("operations"); + HashSet unknownCodes = new HashSet(); + for (CodegenOperation operation : opsMap.get("operation")) { + for (CodegenResponse response : operation.responses) { + if ("default".equals(response.code)) { + continue; + } + try { + int code = Integer.parseInt(response.code); + if (code >= 600) { + unknownCodes.add(code); + } + } catch (NumberFormatException e) { + LOGGER.error("Status code is not an integer : response.code", e); + } + } + } + if (!unknownCodes.isEmpty()) { + additionalProperties.put("unknownStatusCodes", unknownCodes); + } + } catch (Exception e) { + LOGGER.error("Unable to find operations List", e); + } + } + return super.postProcessOperations(objs); + } + + @Override + public List fromSecurity(Map schemes) { + final List codegenSecurities = super.fromSecurity(schemes); + if (!removeOAuthSecurities) { + return codegenSecurities; + } + + // Remove OAuth securities + Iterator it = codegenSecurities.iterator(); + while (it.hasNext()) { + final CodegenSecurity security = it.next(); + if (security.isOAuth) { + it.remove(); + } + } + // Adapt 'hasMore' + it = codegenSecurities.iterator(); + while (it.hasNext()) { + final CodegenSecurity security = it.next(); + security.hasMore = it.hasNext(); + } + + if (codegenSecurities.isEmpty()) { + return null; + } + return codegenSecurities; + } + + @Override + public String toOperationId(String operationId) { + // throw exception if method name is empty + if (StringUtils.isEmpty(operationId)) { + throw new RuntimeException("Empty method name (operationId) not allowed"); + } + + return super.toOperationId(CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, operationId)); + } + + @Override + public String toParamName(String name) { + return formatIdentifier(name, false); + } + + @Override + public String toVarName(String name) { + return formatIdentifier(name, false); + } + + @Override + public String toEnumName(CodegenProperty property) { + return formatIdentifier(property.baseName, true); + } + + @Override + public String toDefaultValue(Schema p) { + if (p.getRequired() != null && p.getRequired().contains(p.getName())) { + return "None"; + } + if (p instanceof StringSchema) { + return "null"; + } else if (p instanceof BooleanSchema) { + return "null"; + } else if (p instanceof DateSchema) { + return "null"; + } else if (p instanceof DateTimeSchema) { + return "null"; + } else if (p instanceof NumberSchema) { + return "null"; + } else if (p instanceof IntegerSchema) { + return "null"; + } else if (p instanceof MapSchema) { + MapSchema ap = (MapSchema) p; + String inner = getSchemaType((Schema) ap.getAdditionalProperties()); + return "Map[String, " + inner + "].empty "; + } else if (p instanceof ArraySchema) { + ArraySchema ap = (ArraySchema) p; + String inner = getSchemaType(ap.getItems()); + return "Seq[" + inner + "].empty "; + } else { + return "null"; + } + } + + @Override + public String toModelName(final String name) { + return formatIdentifier(name, true); + } + + private static abstract class CustomLambda implements Mustache.Lambda { + @Override + public void execute(Template.Fragment frag, Writer out) throws IOException { + final StringWriter tempWriter = new StringWriter(); + frag.execute(tempWriter); + out.write(formatFragment(tempWriter.toString())); + } + + public abstract String formatFragment(String fragment); + } + + private static class JavadocLambda extends CustomLambda { + @Override + public String formatFragment(String fragment) { + final String[] lines = fragment.split("\\r?\\n"); + final StringBuilder sb = new StringBuilder(); + sb.append(" /**\n"); + for (String line : lines) { + sb.append(" * ").append(line).append("\n"); + } + sb.append(" */\n"); + return sb.toString(); + } + } + + private static class CapitalizeLambda extends CustomLambda { + @Override + public String formatFragment(String fragment) { + return StringUtils.capitalize(fragment); + } + } + + private static class CamelizeLambda extends CustomLambda { + private final boolean capitalizeFirst; + + public CamelizeLambda(boolean capitalizeFirst) { + this.capitalizeFirst = capitalizeFirst; + } + + @Override + public String formatFragment(String fragment) { + return camelize(fragment, !capitalizeFirst); + } + } + + private class EnumEntryLambda extends CustomLambda { + @Override + public String formatFragment(String fragment) { + return formatIdentifier(fragment, true); + } + } + + @Override + public String escapeQuotationMark(String input) { + // remove " to avoid code injection + return input.replace("\"", ""); + } +} diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaClientCodegen.java new file mode 100644 index 00000000000..20dc744eaab --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaClientCodegen.java @@ -0,0 +1,240 @@ +package org.openapitools.codegen.languages; + +import org.openapitools.codegen.*; + +import java.io.File; +import java.util.Arrays; +import java.util.HashMap; + +import org.apache.commons.lang3.StringUtils; + +public class ScalaClientCodegen extends AbstractScalaCodegen implements CodegenConfig { + protected String authScheme = ""; + protected String gradleWrapperPackage = "gradle.wrapper"; + protected boolean authPreemptive; + protected boolean asyncHttpClient = !authScheme.isEmpty(); + protected String groupId = "io.swagger"; + protected String artifactId = "swagger-scala-client"; + protected String artifactVersion = "1.0.0"; + protected String clientName = "AsyncClient"; + + public ScalaClientCodegen() { + super(); + outputFolder = "generated-code/scala"; + modelTemplateFiles.put("model.mustache", ".scala"); + apiTemplateFiles.put("api.mustache", ".scala"); + embeddedTemplateDir = templateDir = "scala"; + apiPackage = "io.swagger.client.api"; + modelPackage = "io.swagger.client.model"; + + setReservedWordsLowerCase( + Arrays.asList( + // local variable names used in API methods (endpoints) + "path", "contentTypes", "contentType", "queryParams", "headerParams", + "formParams", "postBody", "mp", "basePath", "apiInvoker", + + // scala reserved words + "abstract", "case", "catch", "class", "def", "do", "else", "extends", + "false", "final", "finally", "for", "forSome", "if", "implicit", + "import", "lazy", "match", "new", "null", "object", "override", "package", + "private", "protected", "return", "sealed", "super", "this", "throw", + "trait", "try", "true", "type", "val", "var", "while", "with", "yield") + ); + + additionalProperties.put(CodegenConstants.INVOKER_PACKAGE, invokerPackage); + additionalProperties.put(CodegenConstants.GROUP_ID, groupId); + additionalProperties.put(CodegenConstants.ARTIFACT_ID, artifactId); + additionalProperties.put(CodegenConstants.ARTIFACT_VERSION, artifactVersion); + additionalProperties.put("asyncHttpClient", asyncHttpClient); + additionalProperties.put("authScheme", authScheme); + additionalProperties.put("authPreemptive", authPreemptive); + additionalProperties.put("clientName", clientName); + additionalProperties.put(CodegenConstants.STRIP_PACKAGE_NAME, stripPackageName); + + supportingFiles.add(new SupportingFile("pom.mustache", "", "pom.xml")); + supportingFiles.add(new SupportingFile("apiInvoker.mustache", + (sourceFolder + File.separator + invokerPackage).replace(".", java.io.File.separator), "ApiInvoker.scala")); + supportingFiles.add(new SupportingFile("client.mustache", + (sourceFolder + File.separator + invokerPackage).replace(".", java.io.File.separator), clientName + ".scala")); + supportingFiles.add(new SupportingFile("git_push.sh.mustache", "", "git_push.sh")); + supportingFiles.add(new SupportingFile("gitignore.mustache", "", ".gitignore")); + // gradle settings + supportingFiles.add(new SupportingFile("build.gradle.mustache", "", "build.gradle")); + supportingFiles.add(new SupportingFile("settings.gradle.mustache", "", "settings.gradle")); + supportingFiles.add(new SupportingFile("gradle.properties.mustache", "", "gradle.properties")); + // gradleWrapper files + supportingFiles.add(new SupportingFile("gradlew.mustache", "", "gradlew")); + supportingFiles.add(new SupportingFile("gradlew.bat.mustache", "", "gradlew.bat")); + supportingFiles.add(new SupportingFile("gradle-wrapper.properties.mustache", + gradleWrapperPackage.replace(".", File.separator), "gradle-wrapper.properties")); + supportingFiles.add(new SupportingFile("gradle-wrapper.jar", + gradleWrapperPackage.replace(".", File.separator), "gradle-wrapper.jar")); + + supportingFiles.add(new SupportingFile("build.sbt.mustache", "", "build.sbt")); + + importMapping.remove("List"); + importMapping.remove("Set"); + importMapping.remove("Map"); + + importMapping.put("Date", "java.util.Date"); + importMapping.put("ListBuffer", "scala.collection.mutable.ListBuffer"); + + typeMapping = new HashMap(); + typeMapping.put("enum", "NSString"); + typeMapping.put("array", "List"); + typeMapping.put("set", "Set"); + typeMapping.put("boolean", "Boolean"); + typeMapping.put("string", "String"); + typeMapping.put("int", "Int"); + typeMapping.put("long", "Long"); + typeMapping.put("float", "Float"); + typeMapping.put("byte", "Byte"); + typeMapping.put("short", "Short"); + typeMapping.put("char", "Char"); + typeMapping.put("double", "Double"); + typeMapping.put("object", "Any"); + typeMapping.put("file", "File"); + typeMapping.put("binary", "Array[Byte]"); + typeMapping.put("ByteArray", "Array[Byte]"); + typeMapping.put("ArrayByte", "Array[Byte]"); + typeMapping.put("date-time", "Date"); + typeMapping.put("DateTime", "Date"); + + instantiationTypes.put("array", "ListBuffer"); + instantiationTypes.put("map", "HashMap"); + + cliOptions.add(new CliOption(CodegenConstants.MODEL_PROPERTY_NAMING, CodegenConstants.MODEL_PROPERTY_NAMING_DESC).defaultValue("camelCase")); + } + + @Override + public void processOpts() { + super.processOpts(); + if (additionalProperties.containsKey(CodegenConstants.MODEL_PROPERTY_NAMING)) { + setModelPropertyNaming((String) additionalProperties.get(CodegenConstants.MODEL_PROPERTY_NAMING)); + } + } + + public void setModelPropertyNaming(String naming) { + if ("original".equals(naming) || "camelCase".equals(naming) || + "PascalCase".equals(naming) || "snake_case".equals(naming)) { + this.modelPropertyNaming = naming; + } else { + throw new IllegalArgumentException("Invalid model property naming '" + + naming + "'. Must be 'original', 'camelCase', " + + "'PascalCase' or 'snake_case'"); + } + } + + public String getModelPropertyNaming() { + return this.modelPropertyNaming; + } + + @Override + public String toVarName(String name) { + // sanitize name + name = sanitizeName(name); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. + + if ("_".equals(name)) { + name = "_u"; + } + + // if it's all uppper case, do nothing + if (name.matches("^[A-Z_]*$")) { + return name; + } + + name = getNameUsingModelPropertyNaming(name); + + // for reserved word or word starting with number, append _ + if (isReservedWord(name) || name.matches("^\\d.*")) { + name = escapeReservedWord(name); + } + + return name; + } + + @Override + public String toParamName(String name) { + // should be the same as variable name + return toVarName(name); + } + + public String getNameUsingModelPropertyNaming(String name) { + switch (CodegenConstants.MODEL_PROPERTY_NAMING_TYPE.valueOf(getModelPropertyNaming())) { + case original: + return name; + case camelCase: + return camelize(name, true); + case PascalCase: + return camelize(name); + case snake_case: + return underscore(name); + default: + throw new IllegalArgumentException("Invalid model property naming '" + + name + "'. Must be 'original', 'camelCase', " + + "'PascalCase' or 'snake_case'"); + } + + } + + @Override + public CodegenType getTag() { + return CodegenType.CLIENT; + } + + @Override + public String getName() { + return "scala"; + } + + @Override + public String getHelp() { + return "Generates a Scala client library (beta)."; + } + + @Override + public String toOperationId(String operationId) { + // throw exception if method name is empty + if (StringUtils.isEmpty(operationId)) { + throw new RuntimeException("Empty method name (operationId) not allowed"); + } + + // method name cannot use reserved keyword, e.g. return + if (isReservedWord(operationId)) { + throw new RuntimeException(operationId + " (reserved word) cannot be used as method name"); + } + + return camelize(operationId, true); + } + + @Override + public String toModelName(final String name) { + final String sanitizedName = sanitizeName(modelNamePrefix + this.stripPackageName(name) + modelNameSuffix); + + // camelize the model name + // phone_number => PhoneNumber + final String camelizedName = camelize(sanitizedName); + + // model name cannot use reserved keyword, e.g. return + if (isReservedWord(camelizedName)) { + final String modelName = "Model" + camelizedName; + LOGGER.warn(camelizedName + " (reserved word) cannot be used as model name. Renamed to " + modelName); + return modelName; + } + + // model name starts with number + if (name.matches("^\\d.*")) { + final String modelName = "Model" + camelizedName; // e.g. 200Response => Model200Response (after camelize) + LOGGER.warn(name + " (model name starts with number) cannot be used as model name. Renamed to " + modelName); + return modelName; + } + + return camelizedName; + } + + @Override + public String toEnumName(CodegenProperty property) { + return formatIdentifier(stripPackageName(property.baseName), true); + } + +} diff --git a/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig b/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig index c4ac26b140e..ce36c791300 100644 --- a/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig +++ b/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig @@ -1,4 +1,5 @@ org.openapitools.codegen.languages.AndroidClientCodegen +org.openapitools.codegen.languages.AkkaScalaClientCodegen org.openapitools.codegen.languages.BashClientCodegen org.openapitools.codegen.languages.DartClientCodegen org.openapitools.codegen.languages.ElixirClientCodegen @@ -11,6 +12,7 @@ org.openapitools.codegen.languages.PythonClientCodegen org.openapitools.codegen.languages.RClientCodegen org.openapitools.codegen.languages.Rails5ServerCodegen org.openapitools.codegen.languages.RubyClientCodegen +org.openapitools.codegen.languages.ScalaClientCodegen org.openapitools.codegen.languages.SlimFrameworkServerCodegen org.openapitools.codegen.languages.SilexServerCodegen org.openapitools.codegen.languages.SinatraServerCodegen