From fa73e56c17474645e7385a5eb8eca502bd7bf53a Mon Sep 17 00:00:00 2001 From: Javier Velilla Date: Sat, 22 Jul 2017 05:55:45 -0300 Subject: [PATCH] Enhancements to the Eiffel generator #6057 (#6138) Added shell scripts, batch files to generate Eiffel Petstore samples. Added Setup CI (travis, shippable, etc) to cover the Eiffel Petstore samples. Refactored part of the code into an Abstract Eiffel base class, which can be later used in Eiffel server stub generators. Added Eiffel Style guide to the contribution guidelines so that Eiffel contributors know what to follow in terms of code style Fixed code generator issues. Updated mustaches templates. --- CONTRIBUTING.md | 1 + bin/eiffel-petstore.sh | 31 ++ bin/windows/eiffel-petstore.bat | 10 + .../languages/AbstractEiffelCogegen.java | 507 ++++++++++++++++++ .../languages/EiffelClientCodegen.java | 467 +--------------- .../src/main/resources/Eiffel/README.mustache | 4 +- .../main/resources/Eiffel/api_client.mustache | 2 +- .../framework/auth/authentication.mustache | 2 +- .../src/main/resources/Eiffel/travis.mustache | 20 + 9 files changed, 575 insertions(+), 469 deletions(-) create mode 100644 bin/eiffel-petstore.sh create mode 100644 bin/windows/eiffel-petstore.bat create mode 100644 modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/AbstractEiffelCogegen.java create mode 100644 modules/swagger-codegen/src/main/resources/Eiffel/travis.mustache diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 970257aba6a..023840bf8f2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,6 +42,7 @@ Code change should conform to the programming style guide of the respective lang - C++ (Tizen): https://wiki.tizen.org/Native_Platform_Coding_Idiom_and_Style_Guide#C.2B.2B_Coding_Style - Clojure: https://github.com/bbatsov/clojure-style-guide - Elixir: https://github.com/christopheradams/elixir_style_guide +- Eiffel: https://www.eiffel.org/doc/eiffel/Coding%20Standards - Erlang: https://github.com/inaka/erlang_guidelines - Haskell: https://github.com/tibbe/haskell-style-guide/blob/master/haskell-style.md - Java: https://google.github.io/styleguide/javaguide.html diff --git a/bin/eiffel-petstore.sh b/bin/eiffel-petstore.sh new file mode 100644 index 00000000000..859e45a53bc --- /dev/null +++ b/bin/eiffel-petstore.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +SCRIPT="$0" + +while [ -h "$SCRIPT" ] ; do + ls=`ls -ld "$SCRIPT"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + SCRIPT="$link" + else + SCRIPT=`dirname "$SCRIPT"`/"$link" + fi +done + +if [ ! -d "${APP_DIR}" ]; then + APP_DIR=`dirname "$SCRIPT"`/.. + APP_DIR=`cd "${APP_DIR}"; pwd` +fi + +executable="./modules/swagger-codegen-cli/target/swagger-codegen-cli.jar" + +if [ ! -f "$executable" ] +then + mvn clean package +fi + +# if you've executed sbt assembly previously it will use that instead. +export JAVA_OPTS="${JAVA_OPTS} -XX:MaxPermSize=256M -Xmx1024M -DloggerPath=conf/log4j.properties" +ags="$@ generate -t modules/swagger-codegen/src/main/resources/eiffel -i modules/swagger-codegen/src/test/resources/2_0/petstore-with-fake-endpoints-models-for-testing.yaml -l eiffel -o samples/client/petstore/eiffel/" + +java $JAVA_OPTS -jar $executable $ags \ No newline at end of file diff --git a/bin/windows/eiffel-petstore.bat b/bin/windows/eiffel-petstore.bat new file mode 100644 index 00000000000..9028359e8c3 --- /dev/null +++ b/bin/windows/eiffel-petstore.bat @@ -0,0 +1,10 @@ +set executable=.\modules\swagger-codegen-cli\target\swagger-codegen-cli.jar + +If Not Exist %executable% ( + mvn clean package +) + +REM set JAVA_OPTS=%JAVA_OPTS% -Xmx1024M -DloggerPath=conf/log4j.properties +set ags=generate -i modules\swagger-codegen\src\test\resources\2_0\petstore.yaml -l eiffel -o samples\client\petstore\eiffel + +java %JAVA_OPTS% -jar %executable% %ags% diff --git a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/AbstractEiffelCogegen.java b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/AbstractEiffelCogegen.java new file mode 100644 index 00000000000..c6beb7f3a0c --- /dev/null +++ b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/AbstractEiffelCogegen.java @@ -0,0 +1,507 @@ +package io.swagger.codegen.languages; + +import static com.google.common.base.Strings.isNullOrEmpty; + +import java.io.File; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang3.StringUtils; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Multimap; + +import io.swagger.codegen.CliOption; +import io.swagger.codegen.CodegenConfig; +import io.swagger.codegen.CodegenConstants; +import io.swagger.codegen.CodegenModel; +import io.swagger.codegen.CodegenOperation; +import io.swagger.codegen.CodegenParameter; +import io.swagger.codegen.CodegenProperty; +import io.swagger.codegen.DefaultCodegen; +import io.swagger.codegen.utils.ModelUtils; +import io.swagger.models.properties.ArrayProperty; +import io.swagger.models.properties.MapProperty; +import io.swagger.models.properties.Property; + +public abstract class AbstractEiffelCogegen extends DefaultCodegen implements CodegenConfig { + + private final Set parentModels = new HashSet<>(); + private final Multimap childrenByParent = ArrayListMultimap.create(); + + public AbstractEiffelCogegen(){ + super(); + setReservedWordsLowerCase(Arrays.asList( + // language reserved words + "across", "agent", "alias", "all", "and", "as", "assign", "attribute", "check", "class", "convert", + "create", "Current", "debug", "deferred", "do", "else", "elseif", "end", "ensure", "expanded", "export", + "external", "False", "feature", "from", "frozen", "if", "implies", "inherit", "inspect", "invariant", + "like", "local", "loop", "not", "note", "obsolete", "old", "once", "only", "or", "Precursor", + "redefine", "rename", "require", "rescue", "Result", "retry", "select", "separate", "then", "True", + "TUPLE", "undefine", "until", "variant", "Void", "when", "xor")); + + defaultIncludes = new HashSet(Arrays.asList("map", "array")); + + languageSpecificPrimitives = new HashSet( + Arrays.asList("BOOLEAN", "INTEGER_8", "INTEGER_16", "INTEGER_32", "INTEGER_64", "NATURAL_8", + "NATURAL_16", "NATURAL_32", "NATURAL_64", "REAL_32", "REAL_64")); + + instantiationTypes.clear(); + + typeMapping.clear(); + typeMapping.put("integer", "INTEGER_32"); + typeMapping.put("long", "INTEGER_64"); + typeMapping.put("number", "REAL_32"); + typeMapping.put("float", "REAL_32"); + typeMapping.put("double", "REAL_64"); + typeMapping.put("boolean", "BOOLEAN"); + typeMapping.put("string", "STRING_32"); + typeMapping.put("UUID", "UUID"); // + typeMapping.put("date", "DATE"); + typeMapping.put("DateTime", "DATE_TIME"); + typeMapping.put("date-time", "DATE_TIME"); + typeMapping.put("password", "STRING"); + typeMapping.put("File", "FILE"); + typeMapping.put("file", "FILE"); + typeMapping.put("binary", "STRING_32"); + typeMapping.put("ByteArray", "ARRAY [NATURAL_8]"); + typeMapping.put("object", "ANY"); + typeMapping.put("map", "STRING_TABLE"); + typeMapping.put("array", "LIST"); + typeMapping.put("list", "LIST"); + + instantiationTypes.put("array", "ARRAYED_LIST"); + instantiationTypes.put("list", "ARRAYED_LIST"); + instantiationTypes.put("map", "STRING_TABLE"); + + + cliOptions.clear(); + cliOptions.add(new CliOption(CodegenConstants.PACKAGE_NAME, "Eiffel Cluster name (convention: lowercase).") + .defaultValue("swagger")); + cliOptions + .add(new CliOption(CodegenConstants.PACKAGE_VERSION, "Eiffel package version.").defaultValue("1.0.0")); + cliOptions.add(new CliOption(CodegenConstants.HIDE_GENERATION_TIMESTAMP, + "hides the timestamp when files were generated").defaultValue(Boolean.TRUE.toString())); + } + + @Override + public String escapeReservedWord(String name) { + // Can't start with an underscore, as our fields need to start with an + // UppercaseLetter so that Go treats them as public/visible. + + // Options? + // - MyName + // - AName + // - TheName + // - XName + // - X_Name + // ... or maybe a suffix? + // - Name_ ... think this will work. + if (this.reservedWordsMappings().containsKey(name)) { + return this.reservedWordsMappings().get(name); + } + if (name.matches("^\\d.*")) {// prepend var_ + return "var_" + name; + } + return "var_" + name; + } + + @Override + public String toVarName(String name) { + // replace - with _ e.g. created-at => created_at + name = sanitizeName(name.replaceAll("-", "_")); + + // if it's all uppper case, do nothing + if (name.matches("^[A-Z_]*$")) { + return name; + } + + // pet_id + // petId => pet_id + name = unCamelize(name); + + if (name.startsWith("_")){ + name = "var" + name; + } + + // for reserved word + if (isReservedWord(name)) { + name = escapeReservedWord(name); + } + + // for reserved word or word starting with number, append + if (name.matches("^\\d.*")) { + name = escapeReservedWord(name); + } + + return name; + } + + @Override + public String toParamName(String name) { + // params should be lowercase. E.g. "person: PERSON" + return toVarName(name).toLowerCase(); + } + + @Override + public String toModelName(String name) { + // phone_number => PHONE_NUMBER + return toModelFilename(name).toUpperCase(); + } + + @Override + public String toModelFilename(String name) { + if (!StringUtils.isEmpty(modelNamePrefix)) { + name = modelNamePrefix + "_" + name; + } + + if (!StringUtils.isEmpty(modelNameSuffix)) { + name = name + "_" + modelNameSuffix; + } + + name = sanitizeName(name); + + // model name cannot use reserved keyword, e.g. return + if (isReservedWord(name)) { + LOGGER.warn(name + " (reserved word) cannot be used as model name. Renamed to " + ("model_" + name)); + name = "model_" + name; // e.g. return => ModelReturn (after + // camelize) + } + + // model name starts with number + if (name.matches("^\\d.*")) { + LOGGER.warn(name + " (model name starts with number) cannot be used as model name. Renamed to " + + ("model_" + name)); + name = "model_" + name; // e.g. 200Response => Model200Response + // (after camelize) + } + + return underscore(name); + } + + @Override + public String toApiFilename(String name) { + // replace - with _ e.g. created-at => created_at + name = name.replaceAll("-", "_"); // FIXME: a parameter should not be + // assigned. Also declare the + // methods parameters as 'final'. + + // e.g. PetApi.go => pet_api.go + return underscore(name) + "_api"; + } + + @Override + public String toApiTestFilename(String name) { + return toApiName(name).toLowerCase() + "_test"; + } + + @Override + public String toApiName(String name) { + if (name.length() == 0) { + return "DEFAULT_API"; + } + return name.toUpperCase() + "_API"; + } + + /** + * Overrides postProcessParameter to add a vendor extension + * "x-exportParamName". This is useful when paramName starts with a + * lowercase letter, but we need that param to be exportable (starts with an + * Uppercase letter). + * + * @param parameter + * CodegenParameter object to be processed. + */ + @Override + public void postProcessParameter(CodegenParameter parameter) { + + // Give the base class a chance to process + super.postProcessParameter(parameter); + + char firstChar = parameter.paramName.charAt(0); + + if (Character.isUpperCase(firstChar)) { + // First char is already uppercase, just use paramName. + parameter.vendorExtensions.put("x-exportParamName", parameter.paramName); + + } + + // It's a lowercase first char, let's convert it to uppercase + StringBuilder sb = new StringBuilder(parameter.paramName); + sb.setCharAt(0, Character.toUpperCase(firstChar)); + parameter.vendorExtensions.put("x-exportParamName", sb.toString()); + } + + @Override + public void postProcessModelProperty(CodegenModel model, CodegenProperty property) { + if (!isNullOrEmpty(model.parent)) { + parentModels.add(model.parent); + if (!childrenByParent.containsEntry(model.parent, model)) { + childrenByParent.put(model.parent, model); + } + } + if (!isNullOrEmpty(model.parentSchema)) { + model.parentSchema = model.parentSchema.toLowerCase(); + } + } + + @Override + public String toModelDocFilename(String name) { + return toModelName(name); + } + + @Override + public String toApiDocFilename(String name) { + return toApiName(name); + } + + @Override + public String getTypeDeclaration(Property p) { + if (p instanceof ArrayProperty) { + ArrayProperty ap = (ArrayProperty) p; + Property inner = ap.getItems(); + return "LIST [" + getTypeDeclaration(inner) + "]"; + } else if (p instanceof MapProperty) { + MapProperty mp = (MapProperty) p; + Property inner = mp.getAdditionalProperties(); + + return getSwaggerType(p) + "[" + getTypeDeclaration(inner) + "]"; + } + // return super.getTypeDeclaration(p); + + // Not using the supertype invocation, because we want to UpperCamelize + // the type. + String swaggerType = getSwaggerType(p); + if (typeMapping.containsKey(swaggerType)) { + return typeMapping.get(swaggerType); + } + + if (typeMapping.containsValue(swaggerType)) { + return swaggerType; + } + + if (languageSpecificPrimitives.contains(swaggerType)) { + return swaggerType; + } + + return toModelName(swaggerType); + } + + @Override + public String getSwaggerType(Property p) { + String swaggerType = super.getSwaggerType(p); + String type = null; + if (typeMapping.containsKey(swaggerType)) { + type = typeMapping.get(swaggerType); + if (languageSpecificPrimitives.contains(type)) + return (type); + } else + type = swaggerType; + return type; + } + + @Override + public String toOperationId(String operationId) { + String sanitizedOperationId = sanitizeName(operationId); + + // method name cannot use reserved keyword, e.g. return + if (isReservedWord(sanitizedOperationId)) { + LOGGER.warn(operationId + " (reserved word) cannot be used as method name. Renamed to " + + camelize("call_" + operationId)); + sanitizedOperationId = "call_" + sanitizedOperationId; + } + // method name from updateSomething to update_Something. + sanitizedOperationId = unCamelize(sanitizedOperationId); + + return toEiffelFeatureStyle(sanitizedOperationId); + } + + @Override + public Map postProcessOperations(Map objs) { + @SuppressWarnings("unchecked") + Map objectMap = (Map) objs.get("operations"); + @SuppressWarnings("unchecked") + List operations = (List) objectMap.get("operation"); + for (CodegenOperation operation : operations) { + // http method verb conversion (e.g. PUT => Put) + + operation.httpMethod = camelize(operation.httpMethod.toLowerCase()); + } + + // remove model imports to avoid error + List> imports = (List>) objs.get("imports"); + if (imports == null) + return objs; + + Iterator> iterator = imports.iterator(); + while (iterator.hasNext()) { + String _import = iterator.next().get("import"); + if (_import.startsWith(apiPackage())) + iterator.remove(); + } + // if the return type is not primitive, import encoding/json + for (CodegenOperation operation : operations) { + if (operation.returnBaseType != null && needToImport(operation.returnBaseType)) { + imports.add(createMapping("import", "encoding/json")); + break; // just need to import once + } + } + + // this will only import "fmt" if there are items in pathParams + for (CodegenOperation operation : operations) { + if (operation.pathParams != null && operation.pathParams.size() > 0) { + imports.add(createMapping("import", "fmt")); + break; // just need to import once + } + } + + // recursively add import for mapping one type to multiple imports + List> recursiveImports = (List>) objs.get("imports"); + if (recursiveImports == null) + return objs; + + ListIterator> listIterator = imports.listIterator(); + while (listIterator.hasNext()) { + String _import = listIterator.next().get("import"); + // if the import package happens to be found in the importMapping + // (key) + // add the corresponding import package to the list + if (importMapping.containsKey(_import)) { + listIterator.add(createMapping("import", importMapping.get(_import))); + } + } + + return objs; + } + + @Override + public Map postProcessModels(Map objs) { + // remove model imports to avoid error + 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(); + } + + // recursively add import for mapping one type to multiple imports + List> recursiveImports = (List>) objs.get("imports"); + if (recursiveImports == null) + return objs; + + ListIterator> listIterator = imports.listIterator(); + while (listIterator.hasNext()) { + String _import = listIterator.next().get("import"); + // if the import package happens to be found in the importMapping + // (key) + // add the corresponding import package to the list + if (importMapping.containsKey(_import)) { + listIterator.add(createMapping("import", importMapping.get(_import))); + } + } + + return objs; + } + + @Override + public Map postProcessAllModels(final Map models) { + + final Map processed = super.postProcessAllModels(models); + postProcessParentModels(models); + return processed; + } + + private void postProcessParentModels(final Map models) { + for (final String parent : parentModels) { + final CodegenModel parentModel = ModelUtils.getModelByName(parent, models); + final Collection childrenModels = childrenByParent.get(parent); + for (final CodegenModel child : childrenModels) { + processParentPropertiesInChildModel(parentModel, child); + } + } + } + + /** + * Sets the child property's isInherited flag to true if it is an inherited + * property + */ + private void processParentPropertiesInChildModel(final CodegenModel parent, final CodegenModel child) { + final Map childPropertiesByName = new HashMap<>(child.vars.size()); + for (final CodegenProperty childProperty : child.vars) { + childPropertiesByName.put(childProperty.name, childProperty); + } + for (final CodegenProperty parentProperty : parent.vars) { + final CodegenProperty duplicatedByParent = childPropertiesByName.get(parentProperty.name); + if (duplicatedByParent != null) { + duplicatedByParent.isInherited = true; + } + } + } + + @Override + protected boolean needToImport(String type) { + return !defaultIncludes.contains(type) && !languageSpecificPrimitives.contains(type); + } + + @Override + public String escapeQuotationMark(String input) { + // remove " to avoid code injection + return input.replace("\"", ""); + } + + @Override + public String escapeUnsafeCharacters(String input) { + return input.replace("*/", "*_/").replace("/*", "/_*"); + } + + public Map createMapping(String key, String value) { + Map customImport = new HashMap(); + customImport.put(key, value); + + return customImport; + } + + @Override + public String toInstantiationType(Property p) { + if (p instanceof MapProperty) { + MapProperty ap = (MapProperty) p; + Property additionalProperties2 = ap.getAdditionalProperties(); + String type = additionalProperties2.getType(); + if (null == type) { + LOGGER.error("No Type defined for Additional Property " + additionalProperties2 + "\n" // + + "\tIn Property: " + p); + } + String inner = toModelName(getSwaggerType(additionalProperties2)); + return instantiationTypes.get("map") + " [" + inner + "]"; + } else if (p instanceof ArrayProperty) { + ArrayProperty ap = (ArrayProperty) p; + String inner = toModelName(getSwaggerType(ap.getItems())); + return instantiationTypes.get("array") + " [" + inner + "]"; + } else { + return null; + } + } + + public String unCamelize(String name) { + return name.replaceAll("(.)(\\p{Upper})", "$1_$2").toLowerCase(); + } + + public String toEiffelFeatureStyle(String operationId) { + if (operationId.startsWith("get_")) { + return operationId.substring(4, operationId.length()); + } else { + return operationId; + } + + } + +} diff --git a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/EiffelClientCodegen.java b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/EiffelClientCodegen.java index ba697be3c2c..55c15e27fe2 100644 --- a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/EiffelClientCodegen.java +++ b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/EiffelClientCodegen.java @@ -1,42 +1,16 @@ package io.swagger.codegen.languages; -import static com.google.common.base.Strings.isNullOrEmpty; - import java.io.File; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.ListIterator; -import java.util.Map; -import java.util.Set; import java.util.UUID; -import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.Multimap; - -import io.swagger.codegen.CliOption; -import io.swagger.codegen.CodegenConfig; import io.swagger.codegen.CodegenConstants; -import io.swagger.codegen.CodegenModel; -import io.swagger.codegen.CodegenOperation; -import io.swagger.codegen.CodegenParameter; -import io.swagger.codegen.CodegenProperty; import io.swagger.codegen.CodegenType; -import io.swagger.codegen.DefaultCodegen; import io.swagger.codegen.SupportingFile; -import io.swagger.codegen.utils.ModelUtils; -import io.swagger.models.properties.ArrayProperty; -import io.swagger.models.properties.MapProperty; -import io.swagger.models.properties.Property; -public class EiffelClientCodegen extends DefaultCodegen implements CodegenConfig { +public class EiffelClientCodegen extends AbstractEiffelCogegen { static Logger LOGGER = LoggerFactory.getLogger(EiffelClientCodegen.class); protected String libraryTarget = "swagger_eiffel_client"; @@ -49,9 +23,6 @@ public class EiffelClientCodegen extends DefaultCodegen implements CodegenConfig protected UUID uuid; protected UUID uuidTest; - private final Set parentModels = new HashSet<>(); - private final Multimap childrenByParent = ArrayListMultimap.create(); - @Override public CodegenType getTag() { return CodegenType.CLIENT; @@ -79,59 +50,6 @@ public class EiffelClientCodegen extends DefaultCodegen implements CodegenConfig apiTestTemplateFiles.put("test/api_test.mustache", ".e"); apiDocTemplateFiles.put("api_doc.mustache", ".md"); embeddedTemplateDir = templateDir = "Eiffel"; - - setReservedWordsLowerCase(Arrays.asList( - // language reserved words - "across", "agent", "alias", "all", "and", "as", "assign", "attribute", "check", "class", "convert", - "create", "Current", "debug", "deferred", "do", "else", "elseif", "end", "ensure", "expanded", "export", - "external", "False", "feature", "from", "frozen", "if", "implies", "inherit", "inspect", "invariant", - "like", "local", "loop", "not", "note", "obsolete", "old", "once", "only", "or", "Precursor", - "redefine", "rename", "require", "rescue", "Result", "retry", "select", "separate", "then", "True", - "TUPLE", "undefine", "until", "variant", "Void", "when", "xor")); - - defaultIncludes = new HashSet(Arrays.asList("map", "array")); - - languageSpecificPrimitives = new HashSet( - Arrays.asList("BOOLEAN", "INTEGER_8", "INTEGER_16", "INTEGER_32", "INTEGER_64", "NATURAL_8", - "NATURAL_16", "NATURAL_32", "NATURAL_64", "REAL_32", "REAL_64")); - - instantiationTypes.clear(); - - typeMapping.clear(); - typeMapping.put("integer", "INTEGER_32"); - typeMapping.put("long", "INTEGER_64"); - typeMapping.put("number", "REAL_32"); - typeMapping.put("float", "REAL_32"); - typeMapping.put("double", "REAL_64"); - typeMapping.put("boolean", "BOOLEAN"); - typeMapping.put("string", "STRING_32"); - typeMapping.put("UUID", "UUID"); // - typeMapping.put("date", "DATE"); - typeMapping.put("DateTime", "DATE_TIME"); - typeMapping.put("date-time", "DATE_TIME"); - typeMapping.put("password", "STRING"); - typeMapping.put("File", "FILE"); - typeMapping.put("file", "FILE"); - typeMapping.put("binary", "STRING_32"); - typeMapping.put("ByteArray", "ARRAY [NATURAL_8]"); - typeMapping.put("object", "ANY"); - typeMapping.put("map", "STRING_TABLE"); - typeMapping.put("array", "LIST"); - typeMapping.put("list", "LIST"); - - //instantiationTypes.put("array", "ARRAY"); - //instantiationTypes.put("list", "ARRAYED_LIST"); - //instantiationTypes.put("map", "STRING_TABLE"); - - - cliOptions.clear(); - cliOptions.add(new CliOption(CodegenConstants.PACKAGE_NAME, "Eiffel Cluster name (convention: lowercase).") - .defaultValue("swagger")); - cliOptions - .add(new CliOption(CodegenConstants.PACKAGE_VERSION, "Eiffel package version.").defaultValue("1.0.0")); - cliOptions.add(new CliOption(CodegenConstants.HIDE_GENERATION_TIMESTAMP, - "hides the timestamp when files were generated").defaultValue(Boolean.TRUE.toString())); - } @Override @@ -173,6 +91,7 @@ public class EiffelClientCodegen extends DefaultCodegen implements CodegenConfig final String authFolder = ("src/framework/auth"); final String serializerFolder = ("src/framework/serialization"); supportingFiles.add(new SupportingFile("README.mustache", "", "README.md")); + supportingFiles.add(new SupportingFile("travis.mustache", "", ".travis.yml")); supportingFiles.add(new SupportingFile("ecf.mustache", "", "api_client.ecf")); supportingFiles.add(new SupportingFile("test/ecf_test.mustache", "test", "api_test.ecf")); supportingFiles.add(new SupportingFile("test/application.mustache", "test", "application.e")); @@ -205,24 +124,6 @@ public class EiffelClientCodegen extends DefaultCodegen implements CodegenConfig } - @Override - public String escapeReservedWord(String name) { - // Can't start with an underscore, as our fields need to start with an - // UppercaseLetter so that Go treats them as public/visible. - - // Options? - // - MyName - // - AName - // - TheName - // - XName - // - X_Name - // ... or maybe a suffix? - // - Name_ ... think this will work. - if (this.reservedWordsMappings().containsKey(name)) { - return this.reservedWordsMappings().get(name); - } - return camelize(name) + '_'; - } @Override public String apiFileFolder() { @@ -237,141 +138,6 @@ public class EiffelClientCodegen extends DefaultCodegen implements CodegenConfig return outputFolder + File.separator + "test" + File.separator + "apis"; } - @Override - public String toVarName(String name) { - // replace - with _ e.g. created-at => created_at - name = sanitizeName(name.replaceAll("-", "_")); - - // if it's all uppper case, do nothing - if (name.matches("^[A-Z_]*$")) { - return name; - } - - // pet_id - // petId => pet_id - name = unCamelize(name); - - // for reserved word or word starting with number, append _ - if (isReservedWord(name)) { - name = escapeReservedWord(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) { - // params should be lowercase. E.g. "person: PERSON" - return toVarName(name).toLowerCase(); - } - - @Override - public String toModelName(String name) { - // phone_number => PHONE_NUMBER - return toModelFilename(name).toUpperCase(); - } - - @Override - public String toModelFilename(String name) { - if (!StringUtils.isEmpty(modelNamePrefix)) { - name = modelNamePrefix + "_" + name; - } - - if (!StringUtils.isEmpty(modelNameSuffix)) { - name = name + "_" + modelNameSuffix; - } - - name = sanitizeName(name); - - // model name cannot use reserved keyword, e.g. return - if (isReservedWord(name)) { - LOGGER.warn(name + " (reserved word) cannot be used as model name. Renamed to " + ("model_" + name)); - name = "model_" + name; // e.g. return => ModelReturn (after - // camelize) - } - - // model name starts with number - if (name.matches("^\\d.*")) { - LOGGER.warn(name + " (model name starts with number) cannot be used as model name. Renamed to " - + ("model_" + name)); - name = "model_" + name; // e.g. 200Response => Model200Response - // (after camelize) - } - - return underscore(name); - } - - @Override - public String toApiFilename(String name) { - // replace - with _ e.g. created-at => created_at - name = name.replaceAll("-", "_"); // FIXME: a parameter should not be - // assigned. Also declare the - // methods parameters as 'final'. - - // e.g. PetApi.go => pet_api.go - return underscore(name) + "_api"; - } - - @Override - public String toApiTestFilename(String name) { - return toApiName(name).toLowerCase() + "_test"; - } - - @Override - public String toApiName(String name) { - if (name.length() == 0) { - return "DEFAULT_API"; - } - return name.toUpperCase() + "_API"; - } - - /** - * Overrides postProcessParameter to add a vendor extension - * "x-exportParamName". This is useful when paramName starts with a - * lowercase letter, but we need that param to be exportable (starts with an - * Uppercase letter). - * - * @param parameter - * CodegenParameter object to be processed. - */ - @Override - public void postProcessParameter(CodegenParameter parameter) { - - // Give the base class a chance to process - super.postProcessParameter(parameter); - - char firstChar = parameter.paramName.charAt(0); - - if (Character.isUpperCase(firstChar)) { - // First char is already uppercase, just use paramName. - parameter.vendorExtensions.put("x-exportParamName", parameter.paramName); - - } - - // It's a lowercase first char, let's convert it to uppercase - StringBuilder sb = new StringBuilder(parameter.paramName); - sb.setCharAt(0, Character.toUpperCase(firstChar)); - parameter.vendorExtensions.put("x-exportParamName", sb.toString()); - } - - @Override - public void postProcessModelProperty(CodegenModel model, CodegenProperty property) { - if (!isNullOrEmpty(model.parent)) { - parentModels.add(model.parent); - if (!childrenByParent.containsEntry(model.parent, model)) { - childrenByParent.put(model.parent, model); - } - } - if (!isNullOrEmpty(model.parentSchema)) { - model.parentSchema = model.parentSchema.toLowerCase(); - } - } - @Override public String apiDocFileFolder() { return (outputFolder + "/" + apiDocPath).replace('/', File.separatorChar); @@ -382,206 +148,6 @@ public class EiffelClientCodegen extends DefaultCodegen implements CodegenConfig return (outputFolder + "/" + modelDocPath).replace('/', File.separatorChar); } - @Override - public String toModelDocFilename(String name) { - return toModelName(name); - } - - @Override - public String toApiDocFilename(String name) { - return toApiName(name); - } - - @Override - public String getTypeDeclaration(Property p) { - if (p instanceof ArrayProperty) { - ArrayProperty ap = (ArrayProperty) p; - Property inner = ap.getItems(); - return "LIST [" + getTypeDeclaration(inner) + "]"; - } else if (p instanceof MapProperty) { - MapProperty mp = (MapProperty) p; - Property inner = mp.getAdditionalProperties(); - - return getSwaggerType(p) + "[" + getTypeDeclaration(inner) + "]"; - } - // return super.getTypeDeclaration(p); - - // Not using the supertype invocation, because we want to UpperCamelize - // the type. - String swaggerType = getSwaggerType(p); - if (typeMapping.containsKey(swaggerType)) { - return typeMapping.get(swaggerType); - } - - if (typeMapping.containsValue(swaggerType)) { - return swaggerType; - } - - if (languageSpecificPrimitives.contains(swaggerType)) { - return swaggerType; - } - - return toModelName(swaggerType); - } - - @Override - public String getSwaggerType(Property p) { - String swaggerType = super.getSwaggerType(p); - String type = null; - if (typeMapping.containsKey(swaggerType)) { - type = typeMapping.get(swaggerType); - if (languageSpecificPrimitives.contains(type)) - return (type); - } else - type = swaggerType; - return type; - } - - @Override - public String toOperationId(String operationId) { - String sanitizedOperationId = sanitizeName(operationId); - - // method name cannot use reserved keyword, e.g. return - if (isReservedWord(sanitizedOperationId)) { - LOGGER.warn(operationId + " (reserved word) cannot be used as method name. Renamed to " - + camelize("call_" + operationId)); - sanitizedOperationId = "call_" + sanitizedOperationId; - } - // method name from updateSomething to update_Something. - sanitizedOperationId = unCamelize(sanitizedOperationId); - - return toEiffelFeatureStyle(sanitizedOperationId); - } - - @Override - public Map postProcessOperations(Map objs) { - @SuppressWarnings("unchecked") - Map objectMap = (Map) objs.get("operations"); - @SuppressWarnings("unchecked") - List operations = (List) objectMap.get("operation"); - for (CodegenOperation operation : operations) { - // http method verb conversion (e.g. PUT => Put) - - operation.httpMethod = camelize(operation.httpMethod.toLowerCase()); - } - - // remove model imports to avoid error - List> imports = (List>) objs.get("imports"); - if (imports == null) - return objs; - - Iterator> iterator = imports.iterator(); - while (iterator.hasNext()) { - String _import = iterator.next().get("import"); - if (_import.startsWith(apiPackage())) - iterator.remove(); - } - // if the return type is not primitive, import encoding/json - for (CodegenOperation operation : operations) { - if (operation.returnBaseType != null && needToImport(operation.returnBaseType)) { - imports.add(createMapping("import", "encoding/json")); - break; // just need to import once - } - } - - // this will only import "fmt" if there are items in pathParams - for (CodegenOperation operation : operations) { - if (operation.pathParams != null && operation.pathParams.size() > 0) { - imports.add(createMapping("import", "fmt")); - break; // just need to import once - } - } - - // recursively add import for mapping one type to multiple imports - List> recursiveImports = (List>) objs.get("imports"); - if (recursiveImports == null) - return objs; - - ListIterator> listIterator = imports.listIterator(); - while (listIterator.hasNext()) { - String _import = listIterator.next().get("import"); - // if the import package happens to be found in the importMapping - // (key) - // add the corresponding import package to the list - if (importMapping.containsKey(_import)) { - listIterator.add(createMapping("import", importMapping.get(_import))); - } - } - - return objs; - } - - @Override - public Map postProcessModels(Map objs) { - // remove model imports to avoid error - 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(); - } - - // recursively add import for mapping one type to multiple imports - List> recursiveImports = (List>) objs.get("imports"); - if (recursiveImports == null) - return objs; - - ListIterator> listIterator = imports.listIterator(); - while (listIterator.hasNext()) { - String _import = listIterator.next().get("import"); - // if the import package happens to be found in the importMapping - // (key) - // add the corresponding import package to the list - if (importMapping.containsKey(_import)) { - listIterator.add(createMapping("import", importMapping.get(_import))); - } - } - - return objs; - } - - @Override - public Map postProcessAllModels(final Map models) { - - final Map processed = super.postProcessAllModels(models); - postProcessParentModels(models); - return processed; - } - - private void postProcessParentModels(final Map models) { - for (final String parent : parentModels) { - final CodegenModel parentModel = ModelUtils.getModelByName(parent, models); - final Collection childrenModels = childrenByParent.get(parent); - for (final CodegenModel child : childrenModels) { - processParentPropertiesInChildModel(parentModel, child); - } - } - } - - /** - * Sets the child property's isInherited flag to true if it is an inherited - * property - */ - private void processParentPropertiesInChildModel(final CodegenModel parent, final CodegenModel child) { - final Map childPropertiesByName = new HashMap<>(child.vars.size()); - for (final CodegenProperty childProperty : child.vars) { - childPropertiesByName.put(childProperty.name, childProperty); - } - for (final CodegenProperty parentProperty : parent.vars) { - final CodegenProperty duplicatedByParent = childPropertiesByName.get(parentProperty.name); - if (duplicatedByParent != null) { - duplicatedByParent.isInherited = true; - } - } - } - - @Override - protected boolean needToImport(String type) { - return !defaultIncludes.contains(type) && !languageSpecificPrimitives.contains(type); - } - public void setPackageName(String packageName) { this.packageName = packageName; } @@ -590,34 +156,5 @@ public class EiffelClientCodegen extends DefaultCodegen implements CodegenConfig this.packageVersion = packageVersion; } - @Override - public String escapeQuotationMark(String input) { - // remove " to avoid code injection - return input.replace("\"", ""); - } - @Override - public String escapeUnsafeCharacters(String input) { - return input.replace("*/", "*_/").replace("/*", "/_*"); - } - - public Map createMapping(String key, String value) { - Map customImport = new HashMap(); - customImport.put(key, value); - - return customImport; - } - - public String unCamelize(String name) { - return name.replaceAll("(.)(\\p{Upper})", "$1_$2").toLowerCase(); - } - - public String toEiffelFeatureStyle(String operationId) { - if (operationId.startsWith("get_")) { - return operationId.substring(4, operationId.length()); - } else { - return operationId; - } - - } } diff --git a/modules/swagger-codegen/src/main/resources/Eiffel/README.mustache b/modules/swagger-codegen/src/main/resources/Eiffel/README.mustache index b0e787e8fd9..9143997024d 100644 --- a/modules/swagger-codegen/src/main/resources/Eiffel/README.mustache +++ b/modules/swagger-codegen/src/main/resources/Eiffel/README.mustache @@ -29,12 +29,12 @@ 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}} +{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}*{{classname}}* | [**{{operationId}}**]({{apiDocPath}}/{{classname}}.md#{{operationIdLowerCase}}) | **{{httpMethod}}** {{path}} | {{#summary}}{{summary}}{{/summary}} {{/operation}}{{/operations}}{{/apis}}{{/apiInfo}} ## Documentation For Models -{{#models}}{{#model}} - [{{{classname}}}]({{modelDocPath}}{{{classname}}}.md) +{{#models}}{{#model}} - [{{{classname}}}]({{modelDocPath}}/{{{classname}}}.md) {{/model}}{{/models}} ## Documentation For Authorization diff --git a/modules/swagger-codegen/src/main/resources/Eiffel/api_client.mustache b/modules/swagger-codegen/src/main/resources/Eiffel/api_client.mustache index 1e8845b6f51..330a0c61a2d 100644 --- a/modules/swagger-codegen/src/main/resources/Eiffel/api_client.mustache +++ b/modules/swagger-codegen/src/main/resources/Eiffel/api_client.mustache @@ -1,4 +1,4 @@ -{{>noteInfo}} +{{>noteinfo}} class API_CLIENT diff --git a/modules/swagger-codegen/src/main/resources/Eiffel/framework/auth/authentication.mustache b/modules/swagger-codegen/src/main/resources/Eiffel/framework/auth/authentication.mustache index 19c0b046779..8e274812fb5 100644 --- a/modules/swagger-codegen/src/main/resources/Eiffel/framework/auth/authentication.mustache +++ b/modules/swagger-codegen/src/main/resources/Eiffel/framework/auth/authentication.mustache @@ -1,4 +1,4 @@ -{{>noteInfo}} +{{>noteinfo}} deferred class AUTHENTICATION diff --git a/modules/swagger-codegen/src/main/resources/Eiffel/travis.mustache b/modules/swagger-codegen/src/main/resources/Eiffel/travis.mustache new file mode 100644 index 00000000000..6cb4545088a --- /dev/null +++ b/modules/swagger-codegen/src/main/resources/Eiffel/travis.mustache @@ -0,0 +1,20 @@ +language: eiffel +before_script: + - export current_dir=$(pwd) + - echo current_dir + - cd .. + - wget https://ftp.eiffel.com/pub/beta/nightly/Eiffel_17.11_gpl_100608-linux-x86-64.tar.bz2 + - tar -xvf Eiffel_17.11_gpl_100608-linux-x86-64.tar.bz2 + - export ISE_EIFFEL=$PWD/Eiffel_17.11 + - export ISE_PLATFORM=linux-x86-64 + - export PATH=$PATH:$ISE_EIFFEL/studio/spec/$ISE_PLATFORM/bin + - export PATH=$PATH:$ISE_EIFFEL/tools/spec/$ISE_PLATFORM/bin + - cd $current_dir + +# safelist +branches: + only: + - master + +script: compile_all -ecb -melt -list_failures -clean -options dotnet=false +