diff --git a/pom.xml b/pom.xml index 6521b3fe4a..a48dac44e7 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ io.swagger.codegen.v3 swagger-codegen-generators - 1.0.13-SNAPSHOT + 1.0.14-SNAPSHOT jar @@ -246,8 +246,8 @@ - 3.0.12 - 2.0.15 + 3.0.14-SNAPSHOT + 2.0.16-SNAPSHOT 2.0.10 2.9.10 2.11.1 diff --git a/src/main/java/io/swagger/codegen/v3/generators/DefaultCodegenConfig.java b/src/main/java/io/swagger/codegen/v3/generators/DefaultCodegenConfig.java index b7db406665..e859cd0775 100644 --- a/src/main/java/io/swagger/codegen/v3/generators/DefaultCodegenConfig.java +++ b/src/main/java/io/swagger/codegen/v3/generators/DefaultCodegenConfig.java @@ -169,6 +169,7 @@ public abstract class DefaultCodegenConfig implements CodegenConfig { protected String ignoreFilePathOverride; protected boolean useOas2 = false; + protected boolean copyFistAllOfProperties = false; public List cliOptions() { return cliOptions; @@ -1353,7 +1354,10 @@ else if (schema instanceof ComposedSchema) { if (codegenModel.interfaces == null) { codegenModel.interfaces = new ArrayList(); } - for (int i = 1; i < allOf.size(); i++) { + for (int i = 0; i < allOf.size(); i++) { + if (i == 0 && !copyFistAllOfProperties) { + continue; + } Schema interfaceSchema = allOf.get(i); if (StringUtils.isBlank(interfaceSchema.get$ref())) { continue; @@ -2599,7 +2603,7 @@ public CodegenParameter fromRequestBody(RequestBody body, String name, Schema sc schema.setName(name); codegenModel = fromModel(name, schema, schemas); } - if (codegenModel != null && !codegenModel.emptyVars) { + if (codegenModel != null) { codegenParameter.baseType = codegenModel.classname; codegenParameter.dataType = getTypeDeclaration(codegenModel.classname); imports.add(codegenParameter.dataType); diff --git a/src/main/java/io/swagger/codegen/v3/generators/go/AbstractGoCodegen.java b/src/main/java/io/swagger/codegen/v3/generators/go/AbstractGoCodegen.java new file mode 100644 index 0000000000..df3e11383b --- /dev/null +++ b/src/main/java/io/swagger/codegen/v3/generators/go/AbstractGoCodegen.java @@ -0,0 +1,530 @@ +package io.swagger.codegen.v3.generators.go; + +import io.swagger.codegen.v3.CliOption; +import io.swagger.codegen.v3.CodegenConfig; +import io.swagger.codegen.v3.CodegenConstants; +import io.swagger.codegen.v3.CodegenModel; +import io.swagger.codegen.v3.CodegenOperation; +import io.swagger.codegen.v3.CodegenParameter; +import io.swagger.codegen.v3.CodegenProperty; +import io.swagger.codegen.v3.generators.DefaultCodegenConfig; +import io.swagger.v3.core.util.Yaml; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.media.ArraySchema; +import io.swagger.v3.oas.models.media.MapSchema; +import io.swagger.v3.oas.models.media.Schema; +import org.apache.commons.lang3.StringUtils; + +import com.fasterxml.jackson.core.JsonProcessingException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Arrays; +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 static io.swagger.codegen.v3.generators.handlebars.ExtensionHelper.getBooleanValue; + +public abstract class AbstractGoCodegen extends DefaultCodegenConfig { + + protected static Logger LOGGER = LoggerFactory.getLogger(AbstractGoCodegen.class); + + protected boolean withXml = false; + + protected String packageName = "swagger"; + + public AbstractGoCodegen() { + super(); + + hideGenerationTimestamp = Boolean.FALSE; + + defaultIncludes = new HashSet<>( + Arrays.asList( + "map", + "array") + ); + + languageSpecificPrimitives = new HashSet<>( + Arrays.asList( + "string", + "bool", + "uint", + "uint32", + "uint64", + "int", + "int32", + "int64", + "float32", + "float64", + "complex64", + "complex128", + "rune", + "byte") + ); + + instantiationTypes.clear(); + /*instantiationTypes.put("array", "GoArray"); + instantiationTypes.put("map", "GoMap");*/ + + typeMapping.clear(); + typeMapping.put("integer", "int32"); + typeMapping.put("long", "int64"); + typeMapping.put("number", "float32"); + typeMapping.put("float", "float32"); + typeMapping.put("double", "float64"); + typeMapping.put("boolean", "bool"); + typeMapping.put("string", "string"); + typeMapping.put("UUID", "string"); + typeMapping.put("date", "string"); + typeMapping.put("DateTime", "time.Time"); + typeMapping.put("password", "string"); + typeMapping.put("File", "*os.File"); + typeMapping.put("file", "*os.File"); + // map binary to string as a workaround + // the correct solution is to use []byte + typeMapping.put("binary", "string"); + typeMapping.put("ByteArray", "string"); + typeMapping.put("object", "interface{}"); + typeMapping.put("UUID", "string"); + + importMapping = new HashMap<>(); + + cliOptions.clear(); + cliOptions.add(new CliOption(CodegenConstants.PACKAGE_NAME, "Go package name (convention: lowercase).") + .defaultValue("swagger")); + + cliOptions.add(new CliOption(CodegenConstants.HIDE_GENERATION_TIMESTAMP, CodegenConstants.HIDE_GENERATION_TIMESTAMP_DESC) + .defaultValue(Boolean.TRUE.toString())); + } + + /** + * Escapes a reserved word as defined in the `reservedWords` array. Handle escaping + * those terms here. This logic is only called if a variable matches the reserved words + * + * @return the escaped term + */ + @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 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; + + // camelize (lower first character) the variable name + // pet_id => PetId + name = camelize(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 (name.matches("^\\d.*")) + name = "Var" + name; + + return name; + } + + @Override + public String toParamName(String name) { + // params should be lowerCamelCase. E.g. "person Person", instead of + // "Person Person". + // + // REVISIT: Actually, for idiomatic go, the param name should + // really should just be a letter, e.g. "p Person"), but we'll get + // around to that some other time... Maybe. + return camelize(toVarName(name), true); + } + + @Override + public String toModelName(String name) { + // camelize the model name + // phone_number => PhoneNumber + return camelize(toModel(name)); + } + + @Override + public String toModelFilename(String name) { + return toModel("model_" + name); + } + + public String toModel(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 "api_" + underscore(name); + } + + /** + * 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 nameFirstChar = parameter.paramName.charAt(0); + if (Character.isUpperCase(nameFirstChar)) { + // First char is already uppercase, just use paramName. + parameter.vendorExtensions.put("x-exportParamName", parameter.paramName); + } else { + // It's a lowercase first char, let's convert it to uppercase + StringBuilder sb = new StringBuilder(parameter.paramName); + sb.setCharAt(0, Character.toUpperCase(nameFirstChar)); + parameter.vendorExtensions.put("x-exportParamName", sb.toString()); + } + } + + @Override + public String getTypeDeclaration(Schema schema) { + if(schema instanceof ArraySchema) { + ArraySchema arraySchema = (ArraySchema) schema; + Schema inner = arraySchema.getItems(); + return "[]" + getTypeDeclaration(inner); + } else if (schema instanceof MapSchema && hasSchemaProperties(schema)) { + MapSchema mapSchema = (MapSchema) schema; + Schema inner = (Schema) mapSchema.getAdditionalProperties(); + + return getSchemaType(schema) + "[string]" + getTypeDeclaration(inner); + } + // Not using the supertype invocation, because we want to UpperCamelize + // the type. + String schemaType = getSchemaType(schema); + if (typeMapping.containsKey(schemaType)) { + return typeMapping.get(schemaType); + } + if(typeMapping.containsValue(schemaType)) { + return schemaType; + } + if(languageSpecificPrimitives.contains(schemaType)) { + return schemaType; + } + return toModelName(schemaType); + } + + @Override + public String getSchemaType(Schema schema) { + String schemaType = super.getSchemaType(schema); + String type; + if(typeMapping.containsKey(schemaType)) { + type = typeMapping.get(schemaType); + if(languageSpecificPrimitives.contains(type)) { + return type; + } + } else { + type = schemaType; + } + 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; + } + return camelize(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(); + } + + // 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 + } + } + + boolean addedOptionalImport = false; + boolean addedTimeImport = false; + boolean addedOSImport = false; + for (CodegenOperation operation : operations) { + for (CodegenParameter param : operation.allParams) { + // import "os" if the operation uses files + if (!addedOSImport && param.dataType == "*os.File") { + imports.add(createMapping("import", "os")); + addedOSImport = true; + } + + // import "time" if the operation has a required time parameter. + if (param.required) { + if (!addedTimeImport && param.dataType == "time.Time") { + imports.add(createMapping("import", "time")); + addedTimeImport = true; + } + } + + // import "optionals" package if the parameter is primitive and optional + if (!param.required && getBooleanValue(param, CodegenConstants.IS_PRIMITIVE_TYPE_EXT_NAME)) { + if (!addedOptionalImport) { + imports.add(createMapping("import", "github.com/antihax/optional")); + addedOptionalImport = true; + } + // We need to specially map Time type to the optionals package + if (param.dataType == "time.Time") { + param.vendorExtensions.put("x-optionalDataType", "Time"); + continue; + } + // Map optional type to dataType + param.vendorExtensions.put("x-optionalDataType", param.dataType.substring(0, 1).toUpperCase() + param.dataType.substring(1)); + } + } + } + + // 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(); + } + + boolean addedTimeImport = false; + boolean addedOSImport = false; + List> models = (List>) objs.get("models"); + for (Map m : models) { + Object v = m.get("model"); + if (v instanceof CodegenModel) { + CodegenModel model = (CodegenModel) v; + for (CodegenProperty param : model.vars) { + if (!addedTimeImport && param.baseType == "time.Time") { + imports.add(createMapping("import", "time")); + addedTimeImport = true; + } + if (!addedOSImport && param.baseType == "*os.File") { + imports.add(createMapping("import", "os")); + addedOSImport = true; + } + } + } + } + // 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 postProcessModelsEnum(objs); + } + + @Override + public Map postProcessSupportingFileData(Map objs) { + OpenAPI openAPI = (OpenAPI)objs.get("openapi"); + if(openAPI != null) { + try { + objs.put("swagger-yaml", Yaml.mapper().writeValueAsString(openAPI)); + } catch (JsonProcessingException e) { + LOGGER.error(e.getMessage(), e); + } + } + return super.postProcessSupportingFileData(objs); + } + + @Override + protected boolean needToImport(String type) { + return !defaultIncludes.contains(type) && !languageSpecificPrimitives.contains(type); + } + + public void setPackageName(String packageName) { + this.packageName = packageName; + } + + @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 toEnumValue(String value, String datatype) { + if ("int".equals(datatype) || "double".equals(datatype) || "float".equals(datatype)) { + return value; + } else { + return escapeText(value); + } + } + + @Override + public String toEnumDefaultValue(String value, String datatype) { + return datatype + "_" + value; + } + + @Override + public String toEnumVarName(String name, String datatype) { + if (name.length() == 0) { + return "EMPTY"; + } + + // number + if ("int".equals(datatype) || "double".equals(datatype) || "float".equals(datatype)) { + String varName = name; + varName = varName.replaceAll("-", "MINUS_"); + varName = varName.replaceAll("\\+", "PLUS_"); + varName = varName.replaceAll("\\.", "_DOT_"); + return varName; + } + + // for symbol, e.g. $, # + if (getSymbolName(name) != null) { + return getSymbolName(name).toUpperCase(); + } + + // string + String enumName = sanitizeName(underscore(name).toUpperCase()); + enumName = enumName.replaceFirst("^_", ""); + enumName = enumName.replaceFirst("_$", ""); + + if (isReservedWord(enumName) || enumName.matches("\\d.*")) { // reserved word or starts with number + return escapeReservedWord(enumName); + } else { + return enumName; + } + } + + @Override + public String toEnumName(CodegenProperty property) { + String enumName = underscore(toModelName(property.name)).toUpperCase(); + + // remove [] for array or map of enum + enumName = enumName.replace("[]", ""); + + if (enumName.matches("\\d.*")) { // starts with number + return "_" + enumName; + } else { + return enumName; + } + } + + public void setWithXml(boolean withXml) { + this.withXml = withXml; + } +} diff --git a/src/main/java/io/swagger/codegen/v3/generators/go/GoServerCodegen.java b/src/main/java/io/swagger/codegen/v3/generators/go/GoServerCodegen.java new file mode 100644 index 0000000000..d0347751fd --- /dev/null +++ b/src/main/java/io/swagger/codegen/v3/generators/go/GoServerCodegen.java @@ -0,0 +1,163 @@ +package io.swagger.codegen.v3.generators.go; + +import io.swagger.codegen.v3.CodegenConstants; +import io.swagger.codegen.v3.CodegenProperty; +import io.swagger.codegen.v3.CodegenType; +import io.swagger.codegen.v3.SupportingFile; +import io.swagger.v3.oas.models.media.ArraySchema; +import io.swagger.v3.oas.models.media.MapSchema; +import io.swagger.v3.oas.models.media.Schema; + +import java.io.File; +import java.util.Arrays; + +import org.apache.commons.lang3.StringUtils; + +public class GoServerCodegen extends AbstractGoCodegen { + + protected String apiVersion = "1.0.0"; + protected int serverPort = 8080; + protected String projectName = "swagger-server"; + protected String apiPath = "go"; + + public GoServerCodegen() { + super(); + + // set the output folder here + outputFolder = "generated-code/go"; + + /* + * Models. You can write model files using the modelTemplateFiles map. + * if you want to create one template for file, you can do so here. + * for multiple files for model, just put another entry in the `modelTemplateFiles` with + * a different extension + */ + modelTemplateFiles.put( + "model.mustache", + ".go"); + + /* + * Api classes. You can write classes for each Api file with the apiTemplateFiles map. + * as with models, add multiple entries with different extensions for multiple files per + * class + */ + apiTemplateFiles.put( + "controller-api.mustache", // the template to use + ".go"); // the extension for each file to write + + /* + * Reserved words. Override this with reserved words specific to your language + */ + setReservedWordsLowerCase( + Arrays.asList( + // data type + "string", "bool", "uint", "uint8", "uint16", "uint32", "uint64", + "int", "int8", "int16", "int32", "int64", "float32", "float64", + "complex64", "complex128", "rune", "byte", "uintptr", + + "break", "default", "func", "interface", "select", + "case", "defer", "go", "map", "struct", + "chan", "else", "goto", "package", "switch", + "const", "fallthrough", "if", "range", "type", + "continue", "for", "import", "return", "var", "error", "nil") + // Added "error" as it's used so frequently that it may as well be a keyword + ); + } + + @Override + public String getDefaultTemplateDir() { + return "go-server"; + } + + @Override + public void processOpts() { + super.processOpts(); + + if (StringUtils.isBlank(templateDir)) { + embeddedTemplateDir = templateDir = getTemplateDir(); + } + + if (additionalProperties.containsKey(CodegenConstants.PACKAGE_NAME)) { + setPackageName((String) additionalProperties.get(CodegenConstants.PACKAGE_NAME)); + } + else { + setPackageName("swagger"); + } + + /* + * Additional Properties. These values can be passed to the templates and + * are available in models, apis, and supporting files + */ + additionalProperties.put("apiVersion", apiVersion); + additionalProperties.put("serverPort", serverPort); + additionalProperties.put("apiPath", apiPath); + additionalProperties.put(CodegenConstants.PACKAGE_NAME, packageName); + + modelPackage = packageName; + apiPackage = packageName; + + /* + * Supporting Files. You can write single files for the generator with the + * entire object tree available. If the input file has a suffix of `.mustache + * it will be processed by the template engine. Otherwise, it will be copied + */ + supportingFiles.add(new SupportingFile("swagger.mustache", "api", "swagger.yaml")); + supportingFiles.add(new SupportingFile("main.mustache", "", "main.go")); + supportingFiles.add(new SupportingFile("routers.mustache", apiPath, "routers.go")); + supportingFiles.add(new SupportingFile("logger.mustache", apiPath, "logger.go")); + writeOptional(outputFolder, new SupportingFile("README.mustache", apiPath, "README.md")); + } + + @Override + public String apiPackage() { + return apiPath; + } + + /** + * Configures the type of generator. + * + * @return the CodegenType for this generator + * @see io.swagger.codegen.CodegenType + */ + @Override + public CodegenType getTag() { + return CodegenType.SERVER; + } + + /** + * Configures a friendly name for the generator. This will be used by the generator + * to select the library with the -l flag. + * + * @return the friendly name for the generator + */ + @Override + public String getName() { + return "go-server"; + } + + /** + * Returns human-friendly help for the generator. Provide the consumer with help + * tips, parameters here + * + * @return A string value for the help message + */ + @Override + public String getHelp() { + return "Generates a Go server library using the swagger-tools project. By default, " + + "it will also generate service classes--which you can disable with the `-Dnoservice` environment variable."; + } + + /** + * Location to write api files. You can use the apiPackage() as defined when the class is + * instantiated + */ + @Override + public String apiFileFolder() { + return outputFolder + File.separator + apiPackage().replace('.', File.separatorChar); + } + + @Override + public String modelFileFolder() { + return outputFolder + File.separator + apiPackage().replace('.', File.separatorChar); + } +} diff --git a/src/main/java/io/swagger/codegen/v3/generators/java/AbstractJavaCodegen.java b/src/main/java/io/swagger/codegen/v3/generators/java/AbstractJavaCodegen.java index 0cf6b86f51..6cc4fb1d0e 100644 --- a/src/main/java/io/swagger/codegen/v3/generators/java/AbstractJavaCodegen.java +++ b/src/main/java/io/swagger/codegen/v3/generators/java/AbstractJavaCodegen.java @@ -35,6 +35,7 @@ import java.util.List; import java.util.ListIterator; import java.util.Map; +import java.util.Objects; import java.util.regex.Pattern; import static io.swagger.codegen.v3.CodegenConstants.HAS_ENUMS_EXT_NAME; @@ -1182,7 +1183,12 @@ private static CodegenModel reconcileInlineEnums(CodegenModel codegenModel, Code while (iterator.hasNext()) { CodegenProperty codegenProperty = iterator.next(); isEnum = getBooleanValue(codegenProperty, IS_ENUM_EXT_NAME); - if (isEnum && codegenProperty.equals(parentModelCodegenPropery)) { + // we don't check for the full set of properties as they could be overridden + // e.g. in the child; if we used codegenProperty.equals, the result in this + // case would be `false` resulting on 2 different enums created on parent and + // child classes, used in same method. This means that the child class will use + // the enum defined in the parent, loosing any overridden property + if (isEnum && isSameEnum(codegenProperty, parentModelCodegenPropery)) { // We found an enum in the child class that is // a duplicate of the one in the parent, so remove it. iterator.remove(); @@ -1199,11 +1205,48 @@ private static CodegenModel reconcileInlineEnums(CodegenModel codegenModel, Code count += 1; codegenProperty.getVendorExtensions().put(CodegenConstants.HAS_MORE_EXT_NAME, (count < numVars) ? true : false); } + + if (!codegenProperties.isEmpty()) { + codegenModel.getVendorExtensions().put(CodegenConstants.HAS_VARS_EXT_NAME, true); + codegenModel.getVendorExtensions().put(CodegenConstants.HAS_ENUMS_EXT_NAME, false); + } else { + codegenModel.emptyVars = true; + codegenModel.getVendorExtensions().put(CodegenConstants.HAS_VARS_EXT_NAME, false); + codegenModel.getVendorExtensions().put(CodegenConstants.HAS_ENUMS_EXT_NAME, false); + } + + codegenModel.vars = codegenProperties; } return codegenModel; + + } + protected static boolean isSameEnum(CodegenProperty actual, CodegenProperty other) { + if (actual == null && other == null) { + return true; + } + if ((actual.name == null) ? (other.name != null) : !actual.name.equals(other.name)) { + return false; + } + if ((actual.baseName == null) ? (other.baseName != null) : !actual.baseName.equals(other.baseName)) { + return false; + } + if ((actual.datatype == null) ? (other.datatype != null) : !actual.datatype.equals(other.datatype)) { + return false; + } + if ((actual.datatypeWithEnum == null) ? (other.datatypeWithEnum != null) : !actual.datatypeWithEnum.equals(other.datatypeWithEnum)) { + return false; + } + if ((actual.baseType == null) ? (other.baseType != null) : !actual.baseType.equals(other.baseType)) { + return false; + } + if (!Objects.equals(actual.enumName, other.enumName)) { + return false; + } + return true; + } private static String sanitizePackageName(String packageName) { packageName = packageName.trim(); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. packageName = packageName.replaceAll("[^a-zA-Z0-9_\\.]", "_"); diff --git a/src/main/java/io/swagger/codegen/v3/generators/java/SpringCodegen.java b/src/main/java/io/swagger/codegen/v3/generators/java/SpringCodegen.java index 8f32d99224..c41c91b127 100644 --- a/src/main/java/io/swagger/codegen/v3/generators/java/SpringCodegen.java +++ b/src/main/java/io/swagger/codegen/v3/generators/java/SpringCodegen.java @@ -157,7 +157,7 @@ public void processOpts() { if (additionalProperties.containsKey(DATE_LIBRARY)) { if (additionalProperties.get(DATE_LIBRARY).toString().startsWith("java8")) { - this.setJava8(Boolean.valueOf(additionalProperties.get(JAVA8_MODE).toString())); + this.setJava8(true); } } if (this.java8) { diff --git a/src/main/java/io/swagger/codegen/v3/generators/python/PythonClientCodegen.java b/src/main/java/io/swagger/codegen/v3/generators/python/PythonClientCodegen.java index add037f303..64a8a177db 100644 --- a/src/main/java/io/swagger/codegen/v3/generators/python/PythonClientCodegen.java +++ b/src/main/java/io/swagger/codegen/v3/generators/python/PythonClientCodegen.java @@ -86,6 +86,7 @@ public PythonClientCodegen() { languageSpecificPrimitives.add("datetime"); languageSpecificPrimitives.add("date"); languageSpecificPrimitives.add("object"); + languageSpecificPrimitives.add("binary_type"); instantiationTypes.put("map", "dict"); @@ -236,6 +237,8 @@ public void processOpts() { modelPackage = packageName + "." + modelPackage; apiPackage = packageName + "." + apiPackage; + copyFistAllOfProperties = true; + } private static String dropDots(String str) { diff --git a/src/main/java/io/swagger/codegen/v3/generators/python/PythonFlaskConnexionCodegen.java b/src/main/java/io/swagger/codegen/v3/generators/python/PythonFlaskConnexionCodegen.java index 283d1a15fe..3340fa74ce 100644 --- a/src/main/java/io/swagger/codegen/v3/generators/python/PythonFlaskConnexionCodegen.java +++ b/src/main/java/io/swagger/codegen/v3/generators/python/PythonFlaskConnexionCodegen.java @@ -70,6 +70,7 @@ public PythonFlaskConnexionCodegen() { languageSpecificPrimitives.add("object"); languageSpecificPrimitives.add("byte"); languageSpecificPrimitives.add("bytearray"); + languageSpecificPrimitives.add("binary_type"); typeMapping.clear(); typeMapping.put("integer", "int"); diff --git a/src/main/resources/META-INF/services/io.swagger.codegen.v3.CodegenConfig b/src/main/resources/META-INF/services/io.swagger.codegen.v3.CodegenConfig index ec84b34ad4..334aa1dc10 100644 --- a/src/main/resources/META-INF/services/io.swagger.codegen.v3.CodegenConfig +++ b/src/main/resources/META-INF/services/io.swagger.codegen.v3.CodegenConfig @@ -1,6 +1,7 @@ io.swagger.codegen.v3.generators.dotnet.AspNetCoreServerCodegen io.swagger.codegen.v3.generators.dotnet.CSharpClientCodegen io.swagger.codegen.v3.generators.dotnet.CsharpDotNet2ClientCodegen +io.swagger.codegen.v3.generators.go.GoServerCodegen io.swagger.codegen.v3.generators.html.StaticDocCodegen io.swagger.codegen.v3.generators.html.StaticHtmlCodegen io.swagger.codegen.v3.generators.html.StaticHtml2Codegen diff --git a/src/main/resources/handlebars/go-server/README.mustache b/src/main/resources/handlebars/go-server/README.mustache new file mode 100644 index 0000000000..89019f8e21 --- /dev/null +++ b/src/main/resources/handlebars/go-server/README.mustache @@ -0,0 +1,30 @@ +# Go API Server for {{packageName}} + +{{#appDescription}} +{{{appDescription}}} +{{/appDescription}} + +## Overview +This server was generated by the [swagger-codegen] +(https://github.com/swagger-api/swagger-codegen) project. +By using the [OpenAPI-Spec](https://github.com/OAI/OpenAPI-Specification) from a remote server, you can easily generate a server stub. +- + +To see how to make this your own, look here: + +[README](https://github.com/swagger-api/swagger-codegen/blob/master/README.md) + +- API version: {{appVersion}}{{^hideGenerationTimestamp}} +- Build date: {{generatedDate}}{{/hideGenerationTimestamp}} +{{#infoUrl}} +For more information, please visit [{{{infoUrl}}}]({{{infoUrl}}}) +{{/infoUrl}} + + +### Running the server +To run the server, follow these simple steps: + +``` +go run main.go +``` + diff --git a/src/main/resources/handlebars/go-server/controller-api.mustache b/src/main/resources/handlebars/go-server/controller-api.mustache new file mode 100644 index 0000000000..528e71fa21 --- /dev/null +++ b/src/main/resources/handlebars/go-server/controller-api.mustache @@ -0,0 +1,12 @@ +{{>partial_header}} +package {{packageName}} + +{{#operations}} +import ( + "net/http" +){{#operation}} + +func {{nickname}}(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + w.WriteHeader(http.StatusOK) +}{{/operation}}{{/operations}} diff --git a/src/main/resources/handlebars/go-server/logger.mustache b/src/main/resources/handlebars/go-server/logger.mustache new file mode 100644 index 0000000000..aa0d894d83 --- /dev/null +++ b/src/main/resources/handlebars/go-server/logger.mustache @@ -0,0 +1,24 @@ +{{>partial_header}} +package {{packageName}} + +import ( + "log" + "net/http" + "time" +) + +func Logger(inner http.Handler, name string) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + start := time.Now() + + inner.ServeHTTP(w, r) + + log.Printf( + "%s %s %s %s", + r.Method, + r.RequestURI, + name, + time.Since(start), + ) + }) +} diff --git a/src/main/resources/handlebars/go-server/main.mustache b/src/main/resources/handlebars/go-server/main.mustache new file mode 100644 index 0000000000..28eb8861b1 --- /dev/null +++ b/src/main/resources/handlebars/go-server/main.mustache @@ -0,0 +1,24 @@ +{{>partial_header}} +package main + +import ( + "log" + "net/http" + + // WARNING! + // Change this to a fully-qualified import path + // once you place this file into your project. + // For example, + // + // sw "github.com/myname/myrepo/{{apiPath}}" + // + sw "./{{apiPath}}" +) + +func main() { + log.Printf("Server started") + + router := sw.NewRouter() + + log.Fatal(http.ListenAndServe(":{{serverPort}}", router)) +} diff --git a/src/main/resources/handlebars/go-server/model.mustache b/src/main/resources/handlebars/go-server/model.mustache new file mode 100644 index 0000000000..7c9e5c88aa --- /dev/null +++ b/src/main/resources/handlebars/go-server/model.mustache @@ -0,0 +1,24 @@ +{{>partial_header}} +package {{packageName}} +{{#models}}{{#imports}} +import ({{/imports}}{{#imports}} + "{{import}}"{{/imports}}{{#imports}} +) +{{/imports}}{{#model}}{{#isEnum}}{{#description}}// {{{classname}}} : {{{description}}}{{/description}} +type {{{name}}} {{^format}}{{dataType}}{{/format}}{{#format}}{{{format}}}{{/format}} + +// List of {{{name}}} +const ( + {{#allowableValues}} + {{#enumVars}} + {{name}} {{{classname}}} = "{{{value}}}" + {{/enumVars}} + {{/allowableValues}} +){{/isEnum}}{{^isEnum}}{{#description}} +// {{{description}}}{{/description}} +type {{classname}} struct { +{{#vars}}{{#description}} + // {{{description}}}{{/description}} + {{name}} {{^isEnum}}{{^isPrimitiveType}}{{^isContainer}}{{^isDateTime}}*{{/isDateTime}}{{/isContainer}}{{/isPrimitiveType}}{{/isEnum}}{{{datatype}}} `json:"{{baseName}}{{^required}},omitempty{{/required}}"` +{{/vars}} +}{{/isEnum}}{{/model}}{{/models}} diff --git a/src/main/resources/handlebars/go-server/partial_header.mustache b/src/main/resources/handlebars/go-server/partial_header.mustache new file mode 100644 index 0000000000..d24dfec369 --- /dev/null +++ b/src/main/resources/handlebars/go-server/partial_header.mustache @@ -0,0 +1,17 @@ +/* + {{#appName}} + * {{{appName}}} + * + {{/appName}} + {{#appDescription}} + * {{{appDescription}}} + * + {{/appDescription}} + {{#version}} + * API version: {{{version}}} + {{/version}} + {{#infoEmail}} + * Contact: {{{infoEmail}}} + {{/infoEmail}} + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ diff --git a/src/main/resources/handlebars/go-server/routers.mustache b/src/main/resources/handlebars/go-server/routers.mustache new file mode 100644 index 0000000000..e3f5bdf345 --- /dev/null +++ b/src/main/resources/handlebars/go-server/routers.mustache @@ -0,0 +1,56 @@ +{{>partial_header}} +package {{packageName}} + +import ( + "fmt" + "net/http" + "strings" + + "github.com/gorilla/mux" +) + +type Route struct { + Name string + Method string + Pattern string + HandlerFunc http.HandlerFunc +} + +type Routes []Route + +func NewRouter() *mux.Router { + router := mux.NewRouter().StrictSlash(true) + for _, route := range routes { + var handler http.Handler + handler = route.HandlerFunc + handler = Logger(handler, route.Name) + + router. + Methods(route.Method). + Path(route.Pattern). + Name(route.Name). + Handler(handler) + } + + return router +} + +func Index(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "Hello World!") +} + +var routes = Routes{ + Route{ + "Index", + "GET", + "{{{basePathWithoutHost}}}/", + Index, + },{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}} + + Route{ + "{{operationId}}", + strings.ToUpper("{{httpMethod}}"), + "{{{basePathWithoutHost}}}{{{path}}}", + {{operationId}}, + },{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}} +} diff --git a/src/main/resources/handlebars/go-server/swagger.mustache b/src/main/resources/handlebars/go-server/swagger.mustache new file mode 100644 index 0000000000..51560926bb --- /dev/null +++ b/src/main/resources/handlebars/go-server/swagger.mustache @@ -0,0 +1 @@ +{{{swagger-yaml}}} \ No newline at end of file diff --git a/src/main/resources/mustache/go-server/README.mustache b/src/main/resources/mustache/go-server/README.mustache new file mode 100644 index 0000000000..89019f8e21 --- /dev/null +++ b/src/main/resources/mustache/go-server/README.mustache @@ -0,0 +1,30 @@ +# Go API Server for {{packageName}} + +{{#appDescription}} +{{{appDescription}}} +{{/appDescription}} + +## Overview +This server was generated by the [swagger-codegen] +(https://github.com/swagger-api/swagger-codegen) project. +By using the [OpenAPI-Spec](https://github.com/OAI/OpenAPI-Specification) from a remote server, you can easily generate a server stub. +- + +To see how to make this your own, look here: + +[README](https://github.com/swagger-api/swagger-codegen/blob/master/README.md) + +- API version: {{appVersion}}{{^hideGenerationTimestamp}} +- Build date: {{generatedDate}}{{/hideGenerationTimestamp}} +{{#infoUrl}} +For more information, please visit [{{{infoUrl}}}]({{{infoUrl}}}) +{{/infoUrl}} + + +### Running the server +To run the server, follow these simple steps: + +``` +go run main.go +``` + diff --git a/src/main/resources/mustache/go-server/controller-api.mustache b/src/main/resources/mustache/go-server/controller-api.mustache new file mode 100644 index 0000000000..528e71fa21 --- /dev/null +++ b/src/main/resources/mustache/go-server/controller-api.mustache @@ -0,0 +1,12 @@ +{{>partial_header}} +package {{packageName}} + +{{#operations}} +import ( + "net/http" +){{#operation}} + +func {{nickname}}(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + w.WriteHeader(http.StatusOK) +}{{/operation}}{{/operations}} diff --git a/src/main/resources/mustache/go-server/logger.mustache b/src/main/resources/mustache/go-server/logger.mustache new file mode 100644 index 0000000000..aa0d894d83 --- /dev/null +++ b/src/main/resources/mustache/go-server/logger.mustache @@ -0,0 +1,24 @@ +{{>partial_header}} +package {{packageName}} + +import ( + "log" + "net/http" + "time" +) + +func Logger(inner http.Handler, name string) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + start := time.Now() + + inner.ServeHTTP(w, r) + + log.Printf( + "%s %s %s %s", + r.Method, + r.RequestURI, + name, + time.Since(start), + ) + }) +} diff --git a/src/main/resources/mustache/go-server/main.mustache b/src/main/resources/mustache/go-server/main.mustache new file mode 100644 index 0000000000..28eb8861b1 --- /dev/null +++ b/src/main/resources/mustache/go-server/main.mustache @@ -0,0 +1,24 @@ +{{>partial_header}} +package main + +import ( + "log" + "net/http" + + // WARNING! + // Change this to a fully-qualified import path + // once you place this file into your project. + // For example, + // + // sw "github.com/myname/myrepo/{{apiPath}}" + // + sw "./{{apiPath}}" +) + +func main() { + log.Printf("Server started") + + router := sw.NewRouter() + + log.Fatal(http.ListenAndServe(":{{serverPort}}", router)) +} diff --git a/src/main/resources/mustache/go-server/model.mustache b/src/main/resources/mustache/go-server/model.mustache new file mode 100644 index 0000000000..7c9e5c88aa --- /dev/null +++ b/src/main/resources/mustache/go-server/model.mustache @@ -0,0 +1,24 @@ +{{>partial_header}} +package {{packageName}} +{{#models}}{{#imports}} +import ({{/imports}}{{#imports}} + "{{import}}"{{/imports}}{{#imports}} +) +{{/imports}}{{#model}}{{#isEnum}}{{#description}}// {{{classname}}} : {{{description}}}{{/description}} +type {{{name}}} {{^format}}{{dataType}}{{/format}}{{#format}}{{{format}}}{{/format}} + +// List of {{{name}}} +const ( + {{#allowableValues}} + {{#enumVars}} + {{name}} {{{classname}}} = "{{{value}}}" + {{/enumVars}} + {{/allowableValues}} +){{/isEnum}}{{^isEnum}}{{#description}} +// {{{description}}}{{/description}} +type {{classname}} struct { +{{#vars}}{{#description}} + // {{{description}}}{{/description}} + {{name}} {{^isEnum}}{{^isPrimitiveType}}{{^isContainer}}{{^isDateTime}}*{{/isDateTime}}{{/isContainer}}{{/isPrimitiveType}}{{/isEnum}}{{{datatype}}} `json:"{{baseName}}{{^required}},omitempty{{/required}}"` +{{/vars}} +}{{/isEnum}}{{/model}}{{/models}} diff --git a/src/main/resources/mustache/go-server/partial_header.mustache b/src/main/resources/mustache/go-server/partial_header.mustache new file mode 100644 index 0000000000..d24dfec369 --- /dev/null +++ b/src/main/resources/mustache/go-server/partial_header.mustache @@ -0,0 +1,17 @@ +/* + {{#appName}} + * {{{appName}}} + * + {{/appName}} + {{#appDescription}} + * {{{appDescription}}} + * + {{/appDescription}} + {{#version}} + * API version: {{{version}}} + {{/version}} + {{#infoEmail}} + * Contact: {{{infoEmail}}} + {{/infoEmail}} + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ diff --git a/src/main/resources/mustache/go-server/routers.mustache b/src/main/resources/mustache/go-server/routers.mustache new file mode 100644 index 0000000000..e3f5bdf345 --- /dev/null +++ b/src/main/resources/mustache/go-server/routers.mustache @@ -0,0 +1,56 @@ +{{>partial_header}} +package {{packageName}} + +import ( + "fmt" + "net/http" + "strings" + + "github.com/gorilla/mux" +) + +type Route struct { + Name string + Method string + Pattern string + HandlerFunc http.HandlerFunc +} + +type Routes []Route + +func NewRouter() *mux.Router { + router := mux.NewRouter().StrictSlash(true) + for _, route := range routes { + var handler http.Handler + handler = route.HandlerFunc + handler = Logger(handler, route.Name) + + router. + Methods(route.Method). + Path(route.Pattern). + Name(route.Name). + Handler(handler) + } + + return router +} + +func Index(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "Hello World!") +} + +var routes = Routes{ + Route{ + "Index", + "GET", + "{{{basePathWithoutHost}}}/", + Index, + },{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}} + + Route{ + "{{operationId}}", + strings.ToUpper("{{httpMethod}}"), + "{{{basePathWithoutHost}}}{{{path}}}", + {{operationId}}, + },{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}} +} diff --git a/src/main/resources/mustache/go-server/swagger.mustache b/src/main/resources/mustache/go-server/swagger.mustache new file mode 100644 index 0000000000..51560926bb --- /dev/null +++ b/src/main/resources/mustache/go-server/swagger.mustache @@ -0,0 +1 @@ +{{{swagger-yaml}}} \ No newline at end of file