From 2cfde8bd651d2da0236db1eac7fa7f60ef218e1e Mon Sep 17 00:00:00 2001 From: wing328 Date: Tue, 27 Mar 2018 20:40:30 +0800 Subject: [PATCH 01/29] add go generators --- .../codegen/languages/AbstractGoCodegen.java | 477 ++++++++++++++++++ .../codegen/languages/GoClientCodegen.java | 179 +++++++ .../codegen/languages/GoServerCodegen.java | 161 ++++++ .../codegen/go/GoClientOptionsTest.java | 39 ++ .../openapitools/codegen/go/GoModelTest.java | 262 ++++++++++ .../options/GoClientOptionsProvider.java | 38 ++ .../options/GoServerOptionsProvider.java | 34 ++ 7 files changed, 1190 insertions(+) create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractGoCodegen.java create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/GoClientCodegen.java create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/GoServerCodegen.java create mode 100644 modules/openapi-generator/src/test/java/org/openapitools/codegen/go/GoClientOptionsTest.java create mode 100644 modules/openapi-generator/src/test/java/org/openapitools/codegen/go/GoModelTest.java create mode 100644 modules/openapi-generator/src/test/java/org/openapitools/options/GoClientOptionsProvider.java create mode 100644 modules/openapi-generator/src/test/java/org/openapitools/options/GoServerOptionsProvider.java diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractGoCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractGoCodegen.java new file mode 100644 index 00000000000..dcda9f96f2c --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractGoCodegen.java @@ -0,0 +1,477 @@ +package org.openapitools.codegen.languages; + +import org.openapitools.codegen.*; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.media.*; +import io.swagger.v3.core.util.Yaml; + +import java.util.*; + +import org.apache.commons.lang3.StringUtils; + +import com.fasterxml.jackson.core.JsonProcessingException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class AbstractGoCodegen extends DefaultCodegen implements CodegenConfig { + + protected static Logger LOGGER = LoggerFactory.getLogger(AbstractGoCodegen.class); + + protected boolean withXml = false; + + protected String packageName = "swagger"; + + public AbstractGoCodegen() { + super(); + + 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(); + importMapping.put("time.Time", "time"); + importMapping.put("*os.File", "os"); + + cliOptions.clear(); + cliOptions.add(new CliOption(CodegenConstants.PACKAGE_NAME, "Go package name (convention: lowercase).") + .defaultValue("swagger")); + cliOptions.add(new CliOption(CodegenConstants.HIDE_GENERATION_TIMESTAMP, "hides the timestamp when files were generated") + .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 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 String getTypeDeclaration(Schema p) { + if(p instanceof ArraySchema) { + ArraySchema ap = (ArraySchema) p; + Schema inner = ap.getItems(); + return "[]" + 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); + + // Not using the supertype invocation, because we want to UpperCamelize + // the type. + String openAPIType = getSchemaType(p); + if (typeMapping.containsKey(openAPIType)) { + return typeMapping.get(openAPIType); + } + + if(typeMapping.containsValue(openAPIType)) { + return openAPIType; + } + + if(languageSpecificPrimitives.contains(openAPIType)) { + return openAPIType; + } + + return toModelName(openAPIType); + } + + @Override + public String getSchemaType(Schema p) { + String openAPIType = super.getSchemaType(p); + String type = null; + if(typeMapping.containsKey(openAPIType)) { + type = typeMapping.get(openAPIType); + if(languageSpecificPrimitives.contains(type)) + return (type); + } + else + type = openAPIType; + 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(); + } + + // if their is a return type, import encoding/json and if needed encoding/xml + for (CodegenOperation operation : operations) { + if(operation.returnBaseType != null ) { + imports.add(createMapping("import", "encoding/json")); + if (withXml) + imports.add(createMapping("import", "encoding/xml")); + 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 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/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/GoClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/GoClientCodegen.java new file mode 100644 index 00000000000..353a0a3d7ea --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/GoClientCodegen.java @@ -0,0 +1,179 @@ +package org.openapitools.codegen.languages; + +import org.openapitools.codegen.*; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.media.*; + +import java.io.File; +import java.util.*; + +import org.apache.commons.lang3.StringUtils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class GoClientCodegen extends AbstractGoCodegen { + + protected String packageVersion = "1.0.0"; + protected String apiDocPath = "docs/"; + protected String modelDocPath = "docs/"; + public static final String WITH_XML = "withXml"; + + public GoClientCodegen() { + super(); + + outputFolder = "generated-code/go"; + modelTemplateFiles.put("model.mustache", ".go"); + apiTemplateFiles.put("api.mustache", ".go"); + + modelDocTemplateFiles.put("model_doc.mustache", ".md"); + apiDocTemplateFiles.put("api_doc.mustache", ".md"); + + embeddedTemplateDir = templateDir = "go"; + + 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", "ApiResponse", "nil") + // Added "error" as it's used so frequently that it may as well be a keyword + ); + + cliOptions.add(new CliOption(CodegenConstants.PACKAGE_VERSION, "Go package version.") + .defaultValue("1.0.0")); + cliOptions.add(CliOption.newBoolean(WITH_XML, "whether to include support for application/xml content type and include XML annotations in the model (works with libraries that provide support for JSON and XML)")); + + } + + @Override + public void processOpts() { + super.processOpts(); + + // default HIDE_GENERATION_TIMESTAMP to true + if (!additionalProperties.containsKey(CodegenConstants.HIDE_GENERATION_TIMESTAMP)) { + additionalProperties.put(CodegenConstants.HIDE_GENERATION_TIMESTAMP, Boolean.TRUE.toString()); + } else { + additionalProperties.put(CodegenConstants.HIDE_GENERATION_TIMESTAMP, + Boolean.valueOf(additionalProperties().get(CodegenConstants.HIDE_GENERATION_TIMESTAMP).toString())); + } + + if (additionalProperties.containsKey(CodegenConstants.PACKAGE_NAME)) { + setPackageName((String) additionalProperties.get(CodegenConstants.PACKAGE_NAME)); + } + else { + setPackageName("swagger"); + } + + if (additionalProperties.containsKey(CodegenConstants.PACKAGE_VERSION)) { + setPackageVersion((String) additionalProperties.get(CodegenConstants.PACKAGE_VERSION)); + } + else { + setPackageVersion("1.0.0"); + } + + additionalProperties.put(CodegenConstants.PACKAGE_NAME, packageName); + additionalProperties.put(CodegenConstants.PACKAGE_VERSION, packageVersion); + + additionalProperties.put("apiDocPath", apiDocPath); + additionalProperties.put("modelDocPath", modelDocPath); + + modelPackage = packageName; + apiPackage = packageName; + + supportingFiles.add(new SupportingFile("swagger.mustache", "api", "swagger.yaml")); + supportingFiles.add(new SupportingFile("README.mustache", "", "README.md")); + supportingFiles.add(new SupportingFile("git_push.sh.mustache", "", "git_push.sh")); + supportingFiles.add(new SupportingFile("gitignore.mustache", "", ".gitignore")); + supportingFiles.add(new SupportingFile("configuration.mustache", "", "configuration.go")); + supportingFiles.add(new SupportingFile("client.mustache", "", "client.go")); + supportingFiles.add(new SupportingFile("response.mustache", "", "response.go")); + supportingFiles.add(new SupportingFile(".travis.yml", "", ".travis.yml")); + + if(additionalProperties.containsKey(WITH_XML)) { + setWithXml(Boolean.parseBoolean(additionalProperties.get(WITH_XML).toString())); + if ( withXml ) { + additionalProperties.put(WITH_XML, "true"); + } + } + } + + /** + * Configures the type of generator. + * + * @return the CodegenType for this generator + * @see org.openapitools.codegen.CodegenType + */ + @Override + public CodegenType getTag() { + return CodegenType.CLIENT; + } + + /** + * 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"; + } + + /** + * 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 client library (beta)."; + } + + /** + * 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; + } + + @Override + public String modelFileFolder() { + return outputFolder + File.separator; + } + + @Override + public String apiDocFileFolder() { + return (outputFolder + "/" + apiDocPath).replace('/', File.separatorChar); + } + + @Override + public String modelDocFileFolder() { + return (outputFolder + "/" + modelDocPath).replace('/', File.separatorChar); + } + + @Override + public String toModelDocFilename(String name) { + return toModelName(name); + } + + @Override + public String toApiDocFilename(String name) { + return toApiName(name); + } + + public void setPackageVersion(String packageVersion) { + this.packageVersion = packageVersion; + } + +} diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/GoServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/GoServerCodegen.java new file mode 100644 index 00000000000..31db47fc347 --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/GoServerCodegen.java @@ -0,0 +1,161 @@ +package org.openapitools.codegen.languages; + +import org.openapitools.codegen.*; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.media.*; + +import java.io.File; +import java.util.*; + +import org.apache.commons.lang3.StringUtils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +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 + + /* + * Template Location. This is the location which templates will be read from. The generator + * will use the resource stream to attempt to read the templates. + */ + embeddedTemplateDir = templateDir = "go-server"; + + /* + * 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 void processOpts() { + super.processOpts(); + + 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 org.openapitools.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/modules/openapi-generator/src/test/java/org/openapitools/codegen/go/GoClientOptionsTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/go/GoClientOptionsTest.java new file mode 100644 index 00000000000..975fef86093 --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/go/GoClientOptionsTest.java @@ -0,0 +1,39 @@ +package org.openapitools.codegen.go; + +import org.openapitools.codegen.AbstractOptionsTest; +import org.openapitools.codegen.CodegenConfig; +import org.openapitools.codegen.languages.GoClientCodegen; +import org.openapitools.codegen.options.GoClientOptionsProvider; + +import mockit.Expectations; +import mockit.Tested; + +public class GoClientOptionsTest extends AbstractOptionsTest { + + @Tested + private GoClientCodegen clientCodegen; + + public GoClientOptionsTest() { + super(new GoClientOptionsProvider()); + } + + @Override + protected CodegenConfig getCodegenConfig() { + return clientCodegen; + } + + @SuppressWarnings("unused") + @Override + protected void setExpectations() { + new Expectations(clientCodegen) {{ + clientCodegen.setPackageVersion(GoClientOptionsProvider.PACKAGE_VERSION_VALUE); + times = 1; + clientCodegen.setPackageName(GoClientOptionsProvider.PACKAGE_NAME_VALUE); + times = 1; + clientCodegen.setWithXml(GoClientOptionsProvider.WITH_XML_VALUE); + times = 1; + clientCodegen.setPrependFormOrBodyParameters(Boolean.valueOf(GoClientOptionsProvider.PREPEND_FORM_OR_BODY_PARAMETERS_VALUE)); + times = 1; + }}; + } +} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/go/GoModelTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/go/GoModelTest.java new file mode 100644 index 00000000000..a43a2615490 --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/go/GoModelTest.java @@ -0,0 +1,262 @@ +package org.openapitools.codegen.go; + +import org.openapitools.codegen.languages.GoClientCodegen; +import org.openapitools.codegen.*; + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.media.*; +import io.swagger.v3.parser.util.SchemaTypeUtil; + +import com.google.common.collect.Sets; +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +@SuppressWarnings("static-method") +public class GoModelTest { + + @Test(description = "convert a simple Go model") + public void simpleModelTest() { + final Schema model = new Schema() + .description("a sample model") + .addProperties("id", new IntegerSchema().format(SchemaTypeUtil.INTEGER64_FORMAT)) + .addProperties("name", new StringSchema()) + .addProperties("createdAt", new DateTimeSchema()) + .addRequiredItem("id") + .addRequiredItem("name"); + final DefaultCodegen codegen = new GoClientCodegen(); + final CodegenModel cm = codegen.fromModel("sample", model); + + Assert.assertEquals(cm.name, "sample"); + Assert.assertEquals(cm.classname, "Sample"); + Assert.assertEquals(cm.description, "a sample model"); + Assert.assertEquals(cm.vars.size(), 3); + Assert.assertEquals(cm.imports.size(), 1); + + final CodegenProperty property1 = cm.vars.get(0); + Assert.assertEquals(property1.baseName, "id"); + Assert.assertEquals(property1.datatype, "int64"); + Assert.assertEquals(property1.name, "Id"); + Assert.assertEquals(property1.defaultValue, "null"); + Assert.assertEquals(property1.baseType, "int64"); + Assert.assertTrue(property1.hasMore); + Assert.assertTrue(property1.required); + Assert.assertTrue(property1.isPrimitiveType); + Assert.assertTrue(property1.isNotContainer); + + final CodegenProperty property2 = cm.vars.get(1); + Assert.assertEquals(property2.baseName, "name"); + Assert.assertEquals(property2.datatype, "string"); + Assert.assertEquals(property2.name, "Name"); + Assert.assertEquals(property2.defaultValue, "null"); + Assert.assertEquals(property2.baseType, "string"); + Assert.assertTrue(property2.hasMore); + Assert.assertTrue(property2.required); + Assert.assertTrue(property2.isPrimitiveType); + Assert.assertTrue(property2.isNotContainer); + + final CodegenProperty property3 = cm.vars.get(2); + Assert.assertEquals(property3.baseName, "createdAt"); + Assert.assertEquals(property3.complexType, "time.Time"); + Assert.assertEquals(property3.datatype, "time.Time"); + Assert.assertEquals(property3.name, "CreatedAt"); + Assert.assertEquals(property3.defaultValue, "null"); + Assert.assertEquals(property3.baseType, "time.Time"); + Assert.assertFalse(property3.hasMore); + Assert.assertFalse(property3.required); + Assert.assertTrue(property3.isNotContainer); + } + + @Test(description = "convert a model with list property") + public void listPropertyTest() { + final Schema model = new Schema() + .description("a sample model") + .addProperties("id", new IntegerSchema().format(SchemaTypeUtil.INTEGER64_FORMAT)) + .addProperties("urls", new ArraySchema() + .items(new StringSchema())) + .addRequiredItem("id"); + final DefaultCodegen codegen = new GoClientCodegen(); + final CodegenModel cm = codegen.fromModel("sample", model); + + Assert.assertEquals(cm.name, "sample"); + Assert.assertEquals(cm.classname, "Sample"); + Assert.assertEquals(cm.description, "a sample model"); + Assert.assertEquals(cm.vars.size(), 2); + + final CodegenProperty property1 = cm.vars.get(0); + Assert.assertEquals(property1.baseName, "id"); + Assert.assertEquals(property1.datatype, "int64"); + Assert.assertEquals(property1.name, "Id"); + Assert.assertEquals(property1.defaultValue, "null"); + Assert.assertEquals(property1.baseType, "int64"); + Assert.assertTrue(property1.hasMore); + Assert.assertTrue(property1.required); + Assert.assertTrue(property1.isPrimitiveType); + Assert.assertTrue(property1.isNotContainer); + + final CodegenProperty property2 = cm.vars.get(1); + Assert.assertEquals(property2.baseName, "urls"); + Assert.assertEquals(property2.datatype, "[]string"); + Assert.assertEquals(property2.name, "Urls"); + Assert.assertEquals(property2.baseType, "array"); + Assert.assertFalse(property2.hasMore); + Assert.assertEquals(property2.containerType, "array"); + Assert.assertFalse(property2.required); + Assert.assertTrue(property2.isPrimitiveType); + Assert.assertTrue(property2.isContainer); + } + + @Test(description = "convert a model with a map property") + public void mapPropertyTest() { + final Schema model = new Schema() + .description("a sample model") + .addProperties("translations", new MapSchema() + .additionalProperties(new StringSchema())) + .addRequiredItem("id"); + final DefaultCodegen codegen = new GoClientCodegen(); + final CodegenModel cm = codegen.fromModel("sample", model); + + Assert.assertEquals(cm.name, "sample"); + Assert.assertEquals(cm.classname, "Sample"); + Assert.assertEquals(cm.description, "a sample model"); + Assert.assertEquals(cm.vars.size(), 1); + + final CodegenProperty property1 = cm.vars.get(0); + Assert.assertEquals(property1.baseName, "translations"); + Assert.assertEquals(property1.datatype, "map[string]string"); + Assert.assertEquals(property1.name, "Translations"); + Assert.assertEquals(property1.baseType, "map"); + Assert.assertEquals(property1.containerType, "map"); + Assert.assertFalse(property1.required); + Assert.assertTrue(property1.isContainer); + Assert.assertTrue(property1.isPrimitiveType); + } + + @Test(description = "convert a model with complex property") + public void complexPropertyTest() { + final Schema model = new Schema() + .description("a sample model") + .addProperties("children", new Schema().$ref("#/definitions/Children")); + final DefaultCodegen codegen = new GoClientCodegen(); + final CodegenModel cm = codegen.fromModel("sample", model); + + Assert.assertEquals(cm.name, "sample"); + Assert.assertEquals(cm.classname, "Sample"); + Assert.assertEquals(cm.description, "a sample model"); + Assert.assertEquals(cm.vars.size(), 1); + + final CodegenProperty property1 = cm.vars.get(0); + Assert.assertEquals(property1.baseName, "children"); + Assert.assertEquals(property1.datatype, "Children"); + Assert.assertEquals(property1.name, "Children"); + Assert.assertEquals(property1.baseType, "Children"); + Assert.assertFalse(property1.required); + Assert.assertTrue(property1.isNotContainer); + } + + @Test(description = "convert a model with complex list property") + public void complexListProperty() { + final Schema model = new Schema() + .description("a sample model") + .addProperties("children", new ArraySchema() + .items(new Schema().$ref("#/definitions/Children"))); + final DefaultCodegen codegen = new GoClientCodegen(); + final CodegenModel cm = codegen.fromModel("sample", model); + + Assert.assertEquals(cm.name, "sample"); + Assert.assertEquals(cm.classname, "Sample"); + Assert.assertEquals(cm.description, "a sample model"); + Assert.assertEquals(cm.vars.size(), 1); + + final CodegenProperty property1 = cm.vars.get(0); + Assert.assertEquals(property1.baseName, "children"); + Assert.assertEquals(property1.datatype, "[]Children"); + Assert.assertEquals(property1.name, "Children"); + Assert.assertEquals(property1.baseType, "array"); + Assert.assertEquals(property1.containerType, "array"); + Assert.assertFalse(property1.required); + Assert.assertTrue(property1.isContainer); + } + + @Test(description = "convert a model with complex map property") + public void complexMapProperty() { + final Schema model = new Schema() + .description("a sample model") + .addProperties("children", new MapSchema() + .additionalProperties(new Schema().$ref("#/definitions/Children"))); + final DefaultCodegen codegen = new GoClientCodegen(); + final CodegenModel cm = codegen.fromModel("sample", model); + + Assert.assertEquals(cm.name, "sample"); + Assert.assertEquals(cm.classname, "Sample"); + Assert.assertEquals(cm.description, "a sample model"); + Assert.assertEquals(cm.vars.size(), 1); + Assert.assertEquals(Sets.intersection(cm.imports, Sets.newHashSet("Children")).size(), 1); + + final CodegenProperty property1 = cm.vars.get(0); + Assert.assertEquals(property1.baseName, "children"); + Assert.assertEquals(property1.complexType, "Children"); + Assert.assertEquals(property1.datatype, "map[string]Children"); + Assert.assertEquals(property1.name, "Children"); + Assert.assertEquals(property1.baseType, "map"); + Assert.assertEquals(property1.containerType, "map"); + Assert.assertFalse(property1.required); + Assert.assertTrue(property1.isContainer); + Assert.assertFalse(property1.isNotContainer); + } + + @Test(description = "convert an array model") + public void arrayModelTest() { + final Schema model = new ArraySchema() + .items(new Schema().$ref("#/definitions/Children")) + .description("an array model"); + final DefaultCodegen codegen = new GoClientCodegen(); + final CodegenModel cm = codegen.fromModel("sample", model); + + Assert.assertEquals(cm.name, "sample"); + Assert.assertEquals(cm.classname, "Sample"); + Assert.assertEquals(cm.description, "an array model"); + Assert.assertEquals(cm.vars.size(), 0); + Assert.assertEquals(cm.imports.size(), 1); + } + + @Test(description = "convert an map model") + public void mapModelTest() { + final Schema model = new Schema() + .additionalProperties(new Schema().$ref("#/definitions/Children")) + .description("a map model"); + final DefaultCodegen codegen = new GoClientCodegen(); + final CodegenModel cm = codegen.fromModel("sample", model); + + Assert.assertEquals(cm.name, "sample"); + Assert.assertEquals(cm.classname, "Sample"); + Assert.assertEquals(cm.description, "a map model"); + Assert.assertEquals(cm.vars.size(), 0); + Assert.assertEquals(cm.imports.size(), 1); + Assert.assertEquals(Sets.intersection(cm.imports, Sets.newHashSet("Children")).size(), 1); + } + + @DataProvider(name = "modelNames") + public static Object[][] primeNumbers() { + return new Object[][] { + {"sample", "Sample"}, + {"sample_name", "SampleName"}, + {"sample__name", "SampleName"}, + {"/sample", "Sample"}, + {"\\sample", "Sample"}, + {"sample.name", "SampleName"}, + {"_sample", "Sample"}, + }; + } + + @Test(dataProvider = "modelNames", description = "avoid inner class") + public void modelNameTest(String name, String expectedName) { + final Schema model = new Schema(); + final DefaultCodegen codegen = new GoClientCodegen(); + final CodegenModel cm = codegen.fromModel(name, model); + + Assert.assertEquals(cm.name, name); + Assert.assertEquals(cm.classname, expectedName); + } +} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/options/GoClientOptionsProvider.java b/modules/openapi-generator/src/test/java/org/openapitools/options/GoClientOptionsProvider.java new file mode 100644 index 00000000000..f95e15df7d7 --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/options/GoClientOptionsProvider.java @@ -0,0 +1,38 @@ +package org.openapitools.codegen.options; + +import org.openapitools.codegen.CodegenConstants; +import org.openapitools.codegen.languages.GoClientCodegen; + +import com.google.common.collect.ImmutableMap; + +import java.util.Map; + +public class GoClientOptionsProvider implements OptionsProvider { + + public static final String PACKAGE_VERSION_VALUE = "1.0.0"; + public static final String PACKAGE_NAME_VALUE = "Go"; + public static final boolean WITH_XML_VALUE = true; + public static final Boolean PREPEND_FORM_OR_BODY_PARAMETERS_VALUE = true; + + @Override + public String getLanguage() { + return "go"; + } + + @Override + public Map createOptions() { + ImmutableMap.Builder builder = new ImmutableMap.Builder(); + return builder + .put(CodegenConstants.PACKAGE_VERSION, PACKAGE_VERSION_VALUE) + .put(CodegenConstants.PACKAGE_NAME, PACKAGE_NAME_VALUE) + .put(CodegenConstants.HIDE_GENERATION_TIMESTAMP, "true") + .put(CodegenConstants.WITH_XML, "true") + .put(CodegenConstants.PREPEND_FORM_OR_BODY_PARAMETERS, "true") + .build(); + } + + @Override + public boolean isServer() { + return false; + } +} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/options/GoServerOptionsProvider.java b/modules/openapi-generator/src/test/java/org/openapitools/options/GoServerOptionsProvider.java new file mode 100644 index 00000000000..35bb7467d54 --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/options/GoServerOptionsProvider.java @@ -0,0 +1,34 @@ +package org.openapitools.codegen.options; + +import org.openapitools.codegen.CodegenConstants; + +import com.google.common.collect.ImmutableMap; + +import java.util.Map; + +public class GoServerOptionsProvider implements OptionsProvider { + public static final String SORT_PARAMS_VALUE = "false"; + public static final String ENSURE_UNIQUE_PARAMS_VALUE = "true"; + public static final String ALLOW_UNICODE_IDENTIFIERS_VALUE = "false"; + public static final String PREPEND_FORM_OR_BODY_PARAMETERS_VALUE = "true"; + + @Override + public String getLanguage() { + return "go-server"; + } + + @Override + public Map createOptions() { + ImmutableMap.Builder builder = new ImmutableMap.Builder(); + return builder.put(CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG, SORT_PARAMS_VALUE) + .put(CodegenConstants.ENSURE_UNIQUE_PARAMS, ENSURE_UNIQUE_PARAMS_VALUE) + .put(CodegenConstants.ALLOW_UNICODE_IDENTIFIERS, ALLOW_UNICODE_IDENTIFIERS_VALUE) + .put(CodegenConstants.PREPEND_FORM_OR_BODY_PARAMETERS, "true") + .build(); + } + + @Override + public boolean isServer() { + return true; + } +} From f89ba9950acb4fe2a0d2f0bf47d39bcbbb9e1472 Mon Sep 17 00:00:00 2001 From: wing328 Date: Tue, 27 Mar 2018 21:35:54 +0800 Subject: [PATCH 02/29] add perl generator --- .../codegen/languages/PerlClientCodegen.java | 415 ++++++++++++++++++ .../org.openapitools.codegen.CodegenConfig | 1 + .../codegen/perl/PerlClientOptionsTest.java | 37 ++ .../options/PerlClientOptionsProvider.java | 36 ++ 4 files changed, 489 insertions(+) create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PerlClientCodegen.java create mode 100644 modules/openapi-generator/src/test/java/org/openapitools/codegen/perl/PerlClientOptionsTest.java create mode 100644 modules/openapi-generator/src/test/java/org/openapitools/options/PerlClientOptionsProvider.java diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PerlClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PerlClientCodegen.java new file mode 100644 index 00000000000..2150e110578 --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PerlClientCodegen.java @@ -0,0 +1,415 @@ +package org.openapitools.codegen.languages; + +import org.openapitools.codegen.CodegenConfig; +import org.openapitools.codegen.CodegenParameter; +import org.openapitools.codegen.CodegenType; +import org.openapitools.codegen.DefaultCodegen; +import org.openapitools.codegen.SupportingFile; +import org.openapitools.codegen.CodegenConstants; +import org.openapitools.codegen.CliOption; + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.media.*; + +import java.io.File; +import java.util.Arrays; +import java.util.HashSet; +import java.util.regex.Matcher; + +import org.apache.commons.lang3.StringUtils; + +public class PerlClientCodegen extends DefaultCodegen implements CodegenConfig { + public static final String MODULE_NAME = "moduleName"; + public static final String MODULE_VERSION = "moduleVersion"; + protected String moduleName = "WWW::SwaggerClient"; + protected String modulePathPart = moduleName.replaceAll("::", Matcher.quoteReplacement(File.separator)); + protected String moduleVersion = "1.0.0"; + protected String apiDocPath = "docs/"; + protected String modelDocPath = "docs/"; + + protected static int emptyFunctionNameCounter = 0; + + public PerlClientCodegen() { + super(); + + // clear import mapping (from default generator) as perl does not use it + // at the moment + importMapping.clear(); + + modelPackage = File.separatorChar + "Object"; + outputFolder = "generated-code" + File.separatorChar + "perl"; + modelTemplateFiles.put("object.mustache", ".pm"); + apiTemplateFiles.put("api.mustache", ".pm"); + modelTestTemplateFiles.put("object_test.mustache", ".t"); + apiTestTemplateFiles.put("api_test.mustache", ".t"); + modelDocTemplateFiles.put("object_doc.mustache", ".md"); + apiDocTemplateFiles.put("api_doc.mustache", ".md"); + embeddedTemplateDir = templateDir = "perl"; + + + setReservedWordsLowerCase( + Arrays.asList( + "else", "lock", "qw", + "__END__", "elsif", "lt", "qx", + "__FILE__", "eq", "m", "s", + "__LINE__", "exp", "ne", "sub", + "__PACKAGE__", "for", "no", "tr", + "and", "foreach", "or", "unless", + "cmp", "ge", "package", "until", + "continue", "gt", "q", "while", + "CORE", "if", "qq", "xor", + "do", "le", "qr", "y", + "return" + ) + ); + + languageSpecificPrimitives.clear(); + languageSpecificPrimitives.add("int"); + languageSpecificPrimitives.add("double"); + languageSpecificPrimitives.add("string"); + languageSpecificPrimitives.add("boolean"); + languageSpecificPrimitives.add("DateTime"); + languageSpecificPrimitives.add("ARRAY"); + languageSpecificPrimitives.add("HASH"); + languageSpecificPrimitives.add("object"); + + typeMapping.clear(); + typeMapping.put("integer", "int"); + typeMapping.put("long", "int"); + typeMapping.put("float", "double"); + typeMapping.put("double", "double"); + typeMapping.put("boolean", "boolean"); + typeMapping.put("string", "string"); + typeMapping.put("date", "DateTime"); + typeMapping.put("DateTime", "DateTime"); + typeMapping.put("password", "string"); + typeMapping.put("array", "ARRAY"); + typeMapping.put("map", "HASH"); + typeMapping.put("object", "object"); + //TODO binary should be mapped to byte array + // mapped to String as a workaround + typeMapping.put("binary", "string"); + typeMapping.put("ByteArray", "string"); + + cliOptions.clear(); + cliOptions.add(new CliOption(MODULE_NAME, "Perl module name (convention: CamelCase or Long::Module).").defaultValue("SwaggerClient")); + cliOptions.add(new CliOption(MODULE_VERSION, "Perl module version.").defaultValue("1.0.0")); + cliOptions.add(CliOption.newBoolean(CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG, + CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG_DESC).defaultValue(Boolean.TRUE.toString())); + cliOptions.add(CliOption.newBoolean(CodegenConstants.ENSURE_UNIQUE_PARAMS, CodegenConstants + .ENSURE_UNIQUE_PARAMS_DESC).defaultValue(Boolean.TRUE.toString())); + cliOptions.add(new CliOption(CodegenConstants.HIDE_GENERATION_TIMESTAMP, CodegenConstants.HIDE_GENERATION_TIMESTAMP_DESC) + .defaultValue(Boolean.TRUE.toString())); + } + + + @Override + public void processOpts() { + super.processOpts(); + + if (additionalProperties.containsKey(MODULE_VERSION)) { + setModuleVersion((String) additionalProperties.get(MODULE_VERSION)); + } else { + additionalProperties.put(MODULE_VERSION, moduleVersion); + } + + if (additionalProperties.containsKey(MODULE_NAME)) { + setModuleName((String) additionalProperties.get(MODULE_NAME)); + setModulePathPart(moduleName.replaceAll("::", Matcher.quoteReplacement(File.separator))); + } else { + additionalProperties.put(MODULE_NAME, moduleName); + } + + // make api and model doc path available in mustache template + additionalProperties.put("apiDocPath", apiDocPath); + additionalProperties.put("modelDocPath", modelDocPath); + + // default HIDE_GENERATION_TIMESTAMP to true + if (!additionalProperties.containsKey(CodegenConstants.HIDE_GENERATION_TIMESTAMP)) { + additionalProperties.put(CodegenConstants.HIDE_GENERATION_TIMESTAMP, Boolean.TRUE.toString()); + } else { + additionalProperties.put(CodegenConstants.HIDE_GENERATION_TIMESTAMP, + Boolean.valueOf(additionalProperties().get(CodegenConstants.HIDE_GENERATION_TIMESTAMP).toString())); + } + + supportingFiles.add(new SupportingFile("ApiClient.mustache", ("lib/" + modulePathPart).replace('/', File.separatorChar), "ApiClient.pm")); + supportingFiles.add(new SupportingFile("Configuration.mustache", ("lib/" + modulePathPart).replace('/', File.separatorChar), "Configuration.pm")); + supportingFiles.add(new SupportingFile("ApiFactory.mustache", ("lib/" + modulePathPart).replace('/', File.separatorChar), "ApiFactory.pm")); + supportingFiles.add(new SupportingFile("Role.mustache", ("lib/" + modulePathPart).replace('/', File.separatorChar), "Role.pm")); + supportingFiles.add(new SupportingFile("AutoDoc.mustache", ("lib/" + modulePathPart + "/Role").replace('/', File.separatorChar), "AutoDoc.pm")); + supportingFiles.add(new SupportingFile("autodoc.script.mustache", "bin", "autodoc")); + supportingFiles.add(new SupportingFile("README.mustache", "", "README.md")); + supportingFiles.add(new SupportingFile("gitignore.mustache", "", ".gitignore")); + supportingFiles.add(new SupportingFile("git_push.sh.mustache", "", "git_push.sh")); + } + + @Override + public CodegenType getTag() { + return CodegenType.CLIENT; + } + + @Override + public String getName() { + return "perl"; + } + + @Override + public String getHelp() { + return "Generates a Perl client library."; + } + + @Override + public String escapeReservedWord(String name) { + if(this.reservedWordsMappings().containsKey(name)) { + return this.reservedWordsMappings().get(name); + } + return "_" + name; + } + + @Override + public String apiFileFolder() { + return (outputFolder + "/lib/" + modulePathPart + apiPackage()).replace('/', File.separatorChar); + } + + @Override + public String modelFileFolder() { + return (outputFolder + "/lib/" + modulePathPart + modelPackage()).replace('/', File.separatorChar); + } + + @Override + public String apiTestFileFolder() { + return (outputFolder + "/t").replace('/', File.separatorChar); + } + + @Override + public String modelTestFileFolder() { + return (outputFolder + "/t").replace('/', File.separatorChar); + } + + @Override + public String apiDocFileFolder() { + return (outputFolder + "/" + apiDocPath).replace('/', File.separatorChar); + } + + @Override + public String modelDocFileFolder() { + return (outputFolder + "/" + modelDocPath).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 type; + } + } else { + type = swaggerType; + } + if (type == null) { + return null; + } + return toModelName(type); + } + + @Override + public String toDefaultValue(Schema p) { + if (p instanceof StringSchema) { + StringSchema dp = (StringSchema) p; + if (dp.getDefault() != null) { + return "'" + dp.getDefault() + "'"; + } + } else if (p instanceof BooleanSchema) { + BooleanSchema dp = (BooleanSchema) p; + if (dp.getDefault() != null) { + return dp.getDefault().toString(); + } + } else if (p instanceof DateSchema) { + // TODO + } else if (p instanceof DateTimeSchema) { + // TODO + } else if (p instanceof NumberSchema) { + NumberSchema dp = (NumberSchema) p; + if (dp.getDefault() != null) { + return dp.getDefault().toString(); + } + } else if (p instanceof IntegerSchema) { + IntegerSchema dp = (IntegerSchema) p; + if (dp.getDefault() != null) { + return dp.getDefault().toString(); + } + } + + return null; + } + + + @Override + public String toVarName(String name) { + // return the name in underscore style + // PhoneNumber => phone_number + name = underscore(name); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. + + // parameter name starting with number won't compile + // need to escape it by appending _ at the beginning + if (name.matches("^\\d.*")) { + name = "_" + name; + } + return name; + } + + @Override + public String toParamName(String name) { + // should be the same as variable name + return toVarName(name); + } + + @Override + public String toModelName(String name) { + name = sanitizeName(name); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. + + // model name cannot use reserved keyword + if (isReservedWord(name)) { + LOGGER.warn(name + " (reserved word) cannot be used as model name. Renamed to " + camelize("model_" + name)); + name = "model_" + name; + } + + // 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 " + camelize("model_" + name)); + name = "model_" + name; // e.g. 200Response => Model200Response (after camelize) + } + + // add prefix/suffic to model name + if (!StringUtils.isEmpty(modelNamePrefix)) { + name = modelNamePrefix + "_" + name; + } + + if (!StringUtils.isEmpty(modelNameSuffix)) { + name = name + "_" + modelNameSuffix; + } + + // camelize the model name + // phone_number => PhoneNumber + return camelize(name); + } + + @Override + public String toModelFilename(String name) { + // should be the same as the model name + return toModelName(name); + } + + @Override + public String toModelTestFilename(String name) { + return toModelFilename(name) + "Test"; + } + + @Override + public String toModelDocFilename(String name) { + return toModelFilename(name); + } + + @Override + public String toApiTestFilename(String name) { + return toApiFilename(name) + "Test"; + } + + @Override + public String toApiDocFilename(String name) { + return toApiFilename(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. phone_number_api.rb => PhoneNumberApi.rb + return camelize(name) + "Api"; + } + + @Override + public String toApiName(String name) { + if (name.length() == 0) { + return "DefaultApi"; + } + // e.g. phone_number_api => PhoneNumberApi + return camelize(name) + "Api"; + } + + @Override + public String toOperationId(String operationId) { + //rename to empty_function_name_1 (e.g.) if method name is empty + if (StringUtils.isEmpty(operationId)) { + operationId = underscore("empty_function_name_" + emptyFunctionNameCounter++); + LOGGER.warn("Empty method name (operationId) found. Renamed to " + operationId); + return operationId; + } + + // method name cannot use reserved keyword, e.g. return + if (isReservedWord(operationId)) { + LOGGER.warn(operationId + " (reserved word) cannot be used as method name. Renamed to " + underscore("call_" + operationId)); + return underscore("call_" + operationId); + } + + //return underscore(operationId).replaceAll("[^A-Za-z0-9_]", ""); + return underscore(sanitizeName(operationId)); + } + + public void setModuleName(String moduleName) { + this.moduleName = moduleName; + } + + public void setModulePathPart(String modulePathPart) { + this.modulePathPart = modulePathPart; + } + + public void setModuleVersion(String moduleVersion) { + this.moduleVersion = moduleVersion; + } + + @Override + public void setParameterExampleValue(CodegenParameter p) { + if (Boolean.TRUE.equals(p.isString) || Boolean.TRUE.equals(p.isBinary) || + Boolean.TRUE.equals(p.isByteArray) || Boolean.TRUE.equals(p.isFile)) { + p.example = "'" + p.example + "'"; + } else if (Boolean.TRUE.equals(p.isBoolean)) { + if (Boolean.parseBoolean(p.example)) + p.example = "1"; + else + p.example = "0"; + } else if (Boolean.TRUE.equals(p.isDateTime) || Boolean.TRUE.equals(p.isDate)) { + p.example = "DateTime->from_epoch(epoch => str2time('" + p.example + "'))"; + } + + } + + @Override + public String escapeQuotationMark(String input) { + return input.replace("'", ""); + } + + @Override + public String escapeUnsafeCharacters(String input) { + // remove =end, =cut to avoid code injection + return input.replace("=begin", "=_begin").replace("=end", "=_end").replace("=cut", "=_cut").replace("=pod", "=_pod"); + } +} 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 4dfb565b97f..fdf9b0bb1c7 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 @@ -4,6 +4,7 @@ org.openapitools.codegen.languages.ElixirClientCodegen org.openapitools.codegen.languages.HaskellServantCodegen org.openapitools.codegen.languages.LumenServerCodegen org.openapitools.codegen.languages.ObjcClientCodegen +org.openapitools.codegen.languages.PerlClientCodegen org.openapitools.codegen.languages.PhpClientCodegen org.openapitools.codegen.languages.PythonClientCodegen org.openapitools.codegen.languages.RClientCodegen diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/perl/PerlClientOptionsTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/perl/PerlClientOptionsTest.java new file mode 100644 index 00000000000..a6c7c05d168 --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/perl/PerlClientOptionsTest.java @@ -0,0 +1,37 @@ +package org.openapitools.codegen.perl; + +import org.openapitools.codegen.AbstractOptionsTest; +import org.openapitools.codegen.CodegenConfig; +import org.openapitools.codegen.languages.PerlClientCodegen; +import org.openapitools.codegen.options.PerlClientOptionsProvider; + +import mockit.Expectations; +import mockit.Tested; + +public class PerlClientOptionsTest extends AbstractOptionsTest { + + @Tested + private PerlClientCodegen clientCodegen; + + public PerlClientOptionsTest() { + super(new PerlClientOptionsProvider()); + } + + @Override + protected CodegenConfig getCodegenConfig() { + return clientCodegen; + } + + @SuppressWarnings("unused") + @Override + protected void setExpectations() { + new Expectations(clientCodegen) {{ + clientCodegen.setModuleName(PerlClientOptionsProvider.MODULE_NAME_VALUE); + times = 1; + clientCodegen.setModuleVersion(PerlClientOptionsProvider.MODULE_VERSION_VALUE); + times = 1; + clientCodegen.setPrependFormOrBodyParameters(Boolean.valueOf(PerlClientOptionsProvider.PREPEND_FORM_OR_BODY_PARAMETERS_VALUE)); + times = 1; + }}; + } +} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/options/PerlClientOptionsProvider.java b/modules/openapi-generator/src/test/java/org/openapitools/options/PerlClientOptionsProvider.java new file mode 100644 index 00000000000..09283b99259 --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/options/PerlClientOptionsProvider.java @@ -0,0 +1,36 @@ +package org.openapitools.codegen.options; + +import org.openapitools.codegen.CodegenConstants; +import org.openapitools.codegen.languages.PerlClientCodegen; + +import com.google.common.collect.ImmutableMap; + +import java.util.Map; + +public class PerlClientOptionsProvider implements OptionsProvider { + public static final String MODULE_NAME_VALUE = ""; + public static final String MODULE_VERSION_VALUE = ""; + public static final String PREPEND_FORM_OR_BODY_PARAMETERS_VALUE = "true"; + + @Override + public String getLanguage() { + return "perl"; + } + + @Override + public Map createOptions() { + ImmutableMap.Builder builder = new ImmutableMap.Builder(); + return builder.put(PerlClientCodegen.MODULE_NAME, MODULE_NAME_VALUE) + .put(PerlClientCodegen.MODULE_VERSION, MODULE_VERSION_VALUE) + .put(CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG, "true") + .put(CodegenConstants.ENSURE_UNIQUE_PARAMS, "true") + .put("hideGenerationTimestamp", "true") + .put(CodegenConstants.PREPEND_FORM_OR_BODY_PARAMETERS, PREPEND_FORM_OR_BODY_PARAMETERS_VALUE) + .build(); + } + + @Override + public boolean isServer() { + return false; + } +} From aa5a7ad54021487ebdcdf84e04666b16ff3f8111 Mon Sep 17 00:00:00 2001 From: wing328 Date: Tue, 27 Mar 2018 22:45:21 +0800 Subject: [PATCH 03/29] 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 From 4a8db170778c6f5cf798f4f163dd78281be08db3 Mon Sep 17 00:00:00 2001 From: wing328 Date: Tue, 27 Mar 2018 22:50:36 +0800 Subject: [PATCH 04/29] add apache2 config generator --- .../codegen/languages/AkkaScalaClientCodegen.java | 4 ++-- .../META-INF/services/org.openapitools.codegen.CodegenConfig | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) 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 index a70aa9f4e8f..582b3737469 100644 --- 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 @@ -150,9 +150,9 @@ public class AkkaScalaClientCodegen extends AbstractScalaCodegen implements Code @Override public String escapeReservedWord(String name) { - if(this.reservedWordsMappings().containsKey(name)) { + if (this.reservedWordsMappings().containsKey(name)) { return this.reservedWordsMappings().get(name); - } + } return "`" + name + "`"; } 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 ce36c791300..a31da16baf5 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.Apache2ConfigCodegen org.openapitools.codegen.languages.AkkaScalaClientCodegen org.openapitools.codegen.languages.BashClientCodegen org.openapitools.codegen.languages.DartClientCodegen From a58e6453720060b7470cb9801dc54757796e13f2 Mon Sep 17 00:00:00 2001 From: wing328 Date: Tue, 27 Mar 2018 22:50:48 +0800 Subject: [PATCH 05/29] add apache 2 config generator --- .../languages/Apache2ConfigCodegen.java | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Apache2ConfigCodegen.java diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Apache2ConfigCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Apache2ConfigCodegen.java new file mode 100644 index 00000000000..1876022d9e4 --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Apache2ConfigCodegen.java @@ -0,0 +1,110 @@ +package org.openapitools.codegen.languages; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; + +import org.openapitools.codegen.CliOption; +import org.openapitools.codegen.CodegenConfig; +import org.openapitools.codegen.CodegenConstants; +import org.openapitools.codegen.CodegenOperation; +import org.openapitools.codegen.CodegenType; +import org.openapitools.codegen.DefaultCodegen; +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.*; + + +public class Apache2ConfigCodegen extends DefaultCodegen implements CodegenConfig { + public static final String USER_INFO_PATH = "userInfoPath"; + protected String userInfoPath = "/var/www/html/"; + + @Override + public CodegenType getTag() { + return CodegenType.CONFIG; + } + + @Override + public String getName() { + return "apache2"; + } + + @Override + public String getHelp() { + return "Generates an Apache2 Config file with the permissions"; + } + + public Apache2ConfigCodegen() { + super(); + apiTemplateFiles.put("apache-config.mustache", ".conf"); + + embeddedTemplateDir = templateDir = "apache2"; + + cliOptions.add(new CliOption(USER_INFO_PATH, "Path to the user and group files")); + } + + + @Override + public void processOpts() { + if (additionalProperties.containsKey(USER_INFO_PATH)) { + userInfoPath = additionalProperties.get(USER_INFO_PATH).toString(); + } + } + + @Override + public String escapeQuotationMark(String input) { + // remove " to avoid code injection + return input.replace("\"", ""); + } + + @Override + public String escapeUnsafeCharacters(String input) { + return input.replace("*/", "*_/").replace("/*", "/_*"); + } + + @SuppressWarnings("unchecked") + @Override + public Map postProcessOperations(Map objs) { + Map operations = (Map) objs.get("operations"); + List operationList = (List) operations.get("operation"); + List newOpList = new ArrayList(); + for (CodegenOperation op : operationList) { + String path = new String(op.path); + + String[] items = path.split("/", -1); + List splitPath = new ArrayList(); + for (String item : items) { + if (item.matches("^\\{(.*)\\}$")) { + item = "*"; + } + splitPath.add(item); + op.path += item + "/"; + } + op.vendorExtensions.put("x-codegen-userInfoPath", userInfoPath); + boolean foundInNewList = false; + for (CodegenOperation op1 : newOpList) { + if (!foundInNewList) { + if (op1.path.equals(op.path)) { + foundInNewList = true; + List currentOtherMethodList = (List) op1.vendorExtensions.get("x-codegen-otherMethods"); + if (currentOtherMethodList == null) { + currentOtherMethodList = new ArrayList(); + } + op.operationIdCamelCase = op1.operationIdCamelCase; + currentOtherMethodList.add(op); + op1.vendorExtensions.put("x-codegen-otherMethods", currentOtherMethodList); + } + } + } + if (!foundInNewList) { + newOpList.add(op); + } + } + operations.put("operation", newOpList); + return objs; + } +} From ee7d3b3b81f359c036c3610f6b7f82b27b5f9ee3 Mon Sep 17 00:00:00 2001 From: wing328 Date: Tue, 27 Mar 2018 23:05:49 +0800 Subject: [PATCH 06/29] add clojure client generator --- .../languages/ClojureClientCodegen.java | 228 ++++++++++++++++++ .../org.openapitools.codegen.CodegenConfig | 1 + 2 files changed, 229 insertions(+) create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ClojureClientCodegen.java diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ClojureClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ClojureClientCodegen.java new file mode 100644 index 00000000000..7fc9bb5d4d4 --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ClojureClientCodegen.java @@ -0,0 +1,228 @@ +package org.openapitools.codegen.languages; + +import org.openapitools.codegen.CliOption; +import org.openapitools.codegen.CodegenConfig; +import org.openapitools.codegen.CodegenConstants; +import org.openapitools.codegen.CodegenOperation; +import org.openapitools.codegen.CodegenType; +import org.openapitools.codegen.DefaultCodegen; +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 io.swagger.v3.oas.models.info.*; + +import org.apache.commons.lang3.StringUtils; + +import java.io.File; +import java.util.Map; +import java.util.List; + +public class ClojureClientCodegen extends DefaultCodegen implements CodegenConfig { + private static final String PROJECT_NAME = "projectName"; + private static final String PROJECT_DESCRIPTION = "projectDescription"; + private static final String PROJECT_VERSION = "projectVersion"; + private static final String PROJECT_URL = "projectUrl"; + private static final String PROJECT_LICENSE_NAME = "projectLicenseName"; + private static final String PROJECT_LICENSE_URL = "projectLicenseUrl"; + private static final String BASE_NAMESPACE = "baseNamespace"; + + protected String projectName; + protected String projectDescription; + protected String projectVersion; + protected String baseNamespace; + + protected String sourceFolder = "src"; + + public ClojureClientCodegen() { + super(); + outputFolder = "generated-code" + File.separator + "clojure"; + apiTemplateFiles.put("api.mustache", ".clj"); + embeddedTemplateDir = templateDir = "clojure"; + + cliOptions.add(new CliOption(PROJECT_NAME, + "name of the project (Default: generated from info.title or \"swagger-clj-client\")")); + cliOptions.add(new CliOption(PROJECT_DESCRIPTION, + "description of the project (Default: using info.description or \"Client library of \")")); + cliOptions.add(new CliOption(PROJECT_VERSION, + "version of the project (Default: using info.version or \"1.0.0\")")); + cliOptions.add(new CliOption(PROJECT_URL, + "URL of the project (Default: using info.contact.url or not included in project.clj)")); + cliOptions.add(new CliOption(PROJECT_LICENSE_NAME, + "name of the license the project uses (Default: using info.license.name or not included in project.clj)")); + cliOptions.add(new CliOption(PROJECT_LICENSE_URL, + "URL of the license the project uses (Default: using info.license.url or not included in project.clj)")); + cliOptions.add(new CliOption(BASE_NAMESPACE, + "the base/top namespace (Default: generated from projectName)")); + } + + @Override + public CodegenType getTag() { + return CodegenType.CLIENT; + } + + @Override + public String getName() { + return "clojure"; + } + + @Override + public String getHelp() { + return "Generates a Clojure client library."; + } + + @Override + public void preprocessOpenAPI(OpenAPI openAPI) { + super.preprocessOpenAPI(openAPI); + + if (additionalProperties.containsKey(PROJECT_NAME)) { + projectName = ((String) additionalProperties.get(PROJECT_NAME)); + } + if (additionalProperties.containsKey(PROJECT_DESCRIPTION)) { + projectDescription = ((String) additionalProperties.get(PROJECT_DESCRIPTION)); + } + if (additionalProperties.containsKey(PROJECT_VERSION)) { + projectVersion = ((String) additionalProperties.get(PROJECT_VERSION)); + } + if (additionalProperties.containsKey(BASE_NAMESPACE)) { + baseNamespace = ((String) additionalProperties.get(BASE_NAMESPACE)); + } + + if (openAPI.getInfo() != null) { + Info info = openAPI.getInfo(); + if (projectName == null && info.getTitle() != null) { + // when projectName is not specified, generate it from info.title + projectName = dashize(info.getTitle()); + } + if (projectVersion == null) { + // when projectVersion is not specified, use info.version + projectVersion = info.getVersion(); + } + if (projectDescription == null) { + // when projectDescription is not specified, use info.description + projectDescription = info.getDescription(); + } + + if (info.getContact() != null) { + Contact contact = info.getContact(); + if (additionalProperties.get(PROJECT_URL) == null) { + additionalProperties.put(PROJECT_URL, contact.getUrl()); + } + } + if (info.getLicense() != null) { + License license = info.getLicense(); + if (additionalProperties.get(PROJECT_LICENSE_NAME) == null) { + additionalProperties.put(PROJECT_LICENSE_NAME, license.getName()); + } + if (additionalProperties.get(PROJECT_LICENSE_URL) == null) { + additionalProperties.put(PROJECT_LICENSE_URL, license.getUrl()); + } + } + } + + // default values + if (projectName == null) { + projectName = "swagger-clj-client"; + } + if (projectVersion == null) { + projectVersion = "1.0.0"; + } + if (projectDescription == null) { + projectDescription = "Client library of " + projectName; + } + if (baseNamespace == null) { + baseNamespace = dashize(projectName); + } + apiPackage = baseNamespace + ".api"; + + additionalProperties.put(PROJECT_NAME, projectName); + additionalProperties.put(PROJECT_DESCRIPTION, escapeText(projectDescription)); + additionalProperties.put(PROJECT_VERSION, projectVersion); + additionalProperties.put(BASE_NAMESPACE, baseNamespace); + additionalProperties.put(CodegenConstants.API_PACKAGE, apiPackage); + + final String baseNamespaceFolder = sourceFolder + File.separator + namespaceToFolder(baseNamespace); + supportingFiles.add(new SupportingFile("project.mustache", "", "project.clj")); + supportingFiles.add(new SupportingFile("core.mustache", baseNamespaceFolder, "core.clj")); + supportingFiles.add(new SupportingFile("git_push.sh.mustache", "", "git_push.sh")); + supportingFiles.add(new SupportingFile("gitignore.mustache", "", ".gitignore")); + } + + @Override + public String sanitizeTag(String tag) { + return tag.replaceAll("[^a-zA-Z_]+", "_"); + } + + @Override + public String apiFileFolder() { + return outputFolder + File.separator + sourceFolder + File.separator + namespaceToFolder(apiPackage); + } + + @Override + public String toOperationId(String operationId) { + // throw exception if method name is empty + if (StringUtils.isEmpty(operationId)) { + throw new RuntimeException("Empty method/operation name (operationId) not allowed"); + } + + return dashize(sanitizeName(operationId)); + } + + @Override + public String toApiFilename(String name) { + return underscore(toApiName(name)); + } + + @Override + public String toApiName(String name) { + return dashize(name); + } + + @Override + public String toParamName(String name) { + return toVarName(name); + } + + @Override + public String toVarName(String name) { + name = name.replaceAll("[^a-zA-Z0-9_-]+", ""); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. + name = dashize(name); + return name; + } + + @Override + public String escapeText(String input) { + if (input == null) { + return null; + } + return input.trim().replace("\\", "\\\\").replace("\"", "\\\""); + } + + @Override + public Map postProcessOperations(Map operations) { + Map objs = (Map) operations.get("operations"); + List ops = (List) objs.get("operation"); + for (CodegenOperation op : ops) { + // Convert httpMethod to lower case, e.g. "get", "post" + op.httpMethod = op.httpMethod.toLowerCase(); + } + return operations; + } + + @SuppressWarnings("static-method") + protected String namespaceToFolder(String ns) { + return ns.replace(".", File.separator).replace("-", "_"); + } + + @Override + public String escapeQuotationMark(String input) { + // remove " to avoid code injection + return input.replace("\"", ""); + } + + @Override + public String escapeUnsafeCharacters(String input) { + // ref: https://clojurebridge.github.io/community-docs/docs/clojure/comment/ + return input.replace("(comment", "(_comment"); + } +} 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 a31da16baf5..7de251cae0c 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 @@ -2,6 +2,7 @@ org.openapitools.codegen.languages.AndroidClientCodegen org.openapitools.codegen.languages.Apache2ConfigCodegen org.openapitools.codegen.languages.AkkaScalaClientCodegen org.openapitools.codegen.languages.BashClientCodegen +org.openapitools.codegen.languages.ClojureClientCodegen org.openapitools.codegen.languages.DartClientCodegen org.openapitools.codegen.languages.ElixirClientCodegen org.openapitools.codegen.languages.HaskellServantCodegen From d07417eeaead86c7519d6c5a3d89cc14e6d437c3 Mon Sep 17 00:00:00 2001 From: wing328 Date: Tue, 27 Mar 2018 23:16:40 +0800 Subject: [PATCH 07/29] add cwiki doc generator --- .../languages/ConfluenceWikiCodegen.java | 107 ++++++++++++++++++ .../org.openapitools.codegen.CodegenConfig | 1 + 2 files changed, 108 insertions(+) create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ConfluenceWikiCodegen.java diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ConfluenceWikiCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ConfluenceWikiCodegen.java new file mode 100644 index 00000000000..a971b7172ff --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ConfluenceWikiCodegen.java @@ -0,0 +1,107 @@ +package org.openapitools.codegen.languages; + +import org.openapitools.codegen.*; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.media.*; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; + +public class ConfluenceWikiCodegen extends DefaultCodegen implements CodegenConfig { + private static final String ALL_OPERATIONS = ""; + protected String invokerPackage = "io.swagger.client"; + protected String groupId = "io.swagger"; + protected String artifactId = "swagger-client"; + protected String artifactVersion = "1.0.0"; + + public ConfluenceWikiCodegen() { + super(); + outputFolder = "docs"; + embeddedTemplateDir = templateDir = "confluenceWikiDocs"; + + defaultIncludes = new HashSet(); + + cliOptions.add(new CliOption("appName", "short name of the application")); + cliOptions.add(new CliOption("appDescription", "description of the application")); + cliOptions.add(new CliOption("infoUrl", "a URL where users can get more information about the application")); + cliOptions.add(new CliOption("infoEmail", "an email address to contact for inquiries about the application")); + cliOptions.add(new CliOption("licenseInfo", "a short description of the license")); + cliOptions.add(new CliOption("licenseUrl", "a URL pointing to the full license")); + cliOptions.add(new CliOption(CodegenConstants.INVOKER_PACKAGE, CodegenConstants.INVOKER_PACKAGE_DESC)); + cliOptions.add(new CliOption(CodegenConstants.GROUP_ID, CodegenConstants.GROUP_ID_DESC)); + cliOptions.add(new CliOption(CodegenConstants.ARTIFACT_ID, CodegenConstants.ARTIFACT_ID_DESC)); + cliOptions.add(new CliOption(CodegenConstants.ARTIFACT_VERSION, CodegenConstants.ARTIFACT_VERSION_DESC)); + + additionalProperties.put("appName", "Swagger Sample"); + additionalProperties.put("appDescription", "A sample swagger server"); + additionalProperties.put("infoUrl", "https://helloreverb.com"); + additionalProperties.put("infoEmail", "hello@helloreverb.com"); + additionalProperties.put("licenseInfo", "All rights reserved"); + additionalProperties.put("licenseUrl", "http://apache.org/licenses/LICENSE-2.0.html"); + additionalProperties.put(CodegenConstants.INVOKER_PACKAGE, invokerPackage); + additionalProperties.put(CodegenConstants.GROUP_ID, groupId); + additionalProperties.put(CodegenConstants.ARTIFACT_ID, artifactId); + additionalProperties.put(CodegenConstants.ARTIFACT_VERSION, artifactVersion); + + supportingFiles.add(new SupportingFile("index.mustache", "", "confluence-markup.txt")); + reservedWords = new HashSet(); + + languageSpecificPrimitives = new HashSet(); + importMapping = new HashMap(); + } + + @Override + public CodegenType getTag() { + return CodegenType.DOCUMENTATION; + } + + @Override + public String getName() { + return "cwiki"; + } + + @Override + public String getHelp() { + return "Generates confluence wiki markup."; + } + + @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 Map postProcessOperations(Map objs) { + Map operations = (Map) objs.get("operations"); + List operationList = (List) operations.get("operation"); + for (CodegenOperation op : operationList) { + op.httpMethod = op.httpMethod.toLowerCase(); + } + return objs; + } + + @Override + public String escapeQuotationMark(String input) { + // just return the original string + return input; + } + + @Override + public String escapeUnsafeCharacters(String input) { + // just return the original string + return input; + } +} 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 7de251cae0c..15403282181 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 @@ -3,6 +3,7 @@ org.openapitools.codegen.languages.Apache2ConfigCodegen org.openapitools.codegen.languages.AkkaScalaClientCodegen org.openapitools.codegen.languages.BashClientCodegen org.openapitools.codegen.languages.ClojureClientCodegen +org.openapitools.codegen.languages.ConfluenceWikiCodegen org.openapitools.codegen.languages.DartClientCodegen org.openapitools.codegen.languages.ElixirClientCodegen org.openapitools.codegen.languages.HaskellServantCodegen From 0b61de9cd5e7134b917acfe270f65e330fde45f0 Mon Sep 17 00:00:00 2001 From: wing328 Date: Wed, 28 Mar 2018 00:25:48 +0800 Subject: [PATCH 08/29] add cpprest generator --- .../openapitools/codegen/DefaultCodegen.java | 20 +- .../codegen/languages/AbstractCppCodegen.java | 182 ++++++++ .../languages/CppRestClientCodegen.java | 429 ++++++++++++++++++ .../org.openapitools.codegen.CodegenConfig | 1 + 4 files changed, 631 insertions(+), 1 deletion(-) create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractCppCodegen.java create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppRestClientCodegen.java diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java index 460bd95015e..c054de79fef 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java @@ -3706,7 +3706,7 @@ public class DefaultCodegen implements CodegenConfig { return mediaType.getSchema(); } - private Schema getSchemaFromResponse(ApiResponse response) { + protected Schema getSchemaFromResponse(ApiResponse response) { if (response.getContent() == null || response.getContent().isEmpty()) { return null; } @@ -4118,4 +4118,22 @@ public class DefaultCodegen implements CodegenConfig { return false; } + protected void addOption(String key, String description) { + addOption(key, description, null); + } + + protected void addOption(String key, String description, String defaultValue) { + CliOption option = new CliOption(key, description); + if (defaultValue != null) + option.defaultValue(defaultValue); + cliOptions.add(option); + } + + protected void addSwitch(String key, String description, Boolean defaultValue) { + CliOption option = CliOption.newBoolean(key, description); + if (defaultValue != null) + option.defaultValue(defaultValue.toString()); + cliOptions.add(option); + } + } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractCppCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractCppCodegen.java new file mode 100644 index 00000000000..07a430abecf --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractCppCodegen.java @@ -0,0 +1,182 @@ +package org.openapitools.codegen.languages; + +import org.openapitools.codegen.CodegenConfig; +import org.openapitools.codegen.CodegenProperty; +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 java.util.Arrays; + +abstract public class AbstractCppCodegen extends DefaultCodegen implements CodegenConfig { + + public AbstractCppCodegen() { + super(); + + /* + * Reserved words. Override this with reserved words specific to your language + */ + setReservedWordsLowerCase( + Arrays.asList( + "alignas", + "alignof", + "and", + "and_eq", + "asm", + "auto", + "bitand", + "bitor", + "bool", + "break", + "case", + "catch", + "char", + "char16_t", + "char32_t", + "class", + "compl", + "concept", + "const", + "constexpr", + "const_cast", + "continue", + "decltype", + "default", + "delete", + "do", + "double", + "dynamic_cast", + "else", + "enum", + "explicit", + "export", + "extern", + "false", + "float", + "for", + "friend", + "goto", + "if", + "inline", + "int", + "linux", + "long", + "mutable", + "namespace", + "new", + "noexcept", + "not", + "not_eq", + "nullptr", + "operator", + "or", + "or_eq", + "private", + "protected", + "public", + "register", + "reinterpret_cast", + "requires", + "return", + "short", + "signed", + "sizeof", + "static", + "static_assert", + "static_cast", + "struct", + "switch", + "template", + "this", + "thread_local", + "throw", + "true", + "try", + "typedef", + "typeid", + "typename", + "union", + "unsigned", + "using", + "virtual", + "void", + "volatile", + "wchar_t", + "while", + "xor", + "xor_eq") + ); + } + + @Override + public String toVarName(String name) { + if (typeMapping.keySet().contains(name) || typeMapping.values().contains(name) + || importMapping.values().contains(name) || defaultIncludes.contains(name) + || languageSpecificPrimitives.contains(name)) { + return sanitizeName(name); + } + + if (isReservedWord(name)) { + return escapeReservedWord(name); + } + + if (name.length() > 1) { + return sanitizeName(Character.toUpperCase(name.charAt(0)) + name.substring(1)); + } + + return sanitizeName(name); + } + + /** + * 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) { + if (this.reservedWordsMappings().containsKey(name)) { + return this.reservedWordsMappings().get(name); + } + return sanitizeName("_" + name); + } + + @Override + public String toOperationId(String operationId) { + if (isReservedWord(operationId)) { + LOGGER.warn(operationId + " (reserved word) cannot be used as method name. Renamed to " + escapeReservedWord(operationId)); + return escapeReservedWord(operationId); + } + return sanitizeName(super.toOperationId(operationId)); + } + + @Override + public String toParamName(String name) { + return sanitizeName(super.toParamName(name)); + } + + @Override + public CodegenProperty fromProperty(String name, Schema p) { + CodegenProperty property = super.fromProperty(name, p); + String nameInCamelCase = property.nameInCamelCase; + if (nameInCamelCase.length() > 1) { + nameInCamelCase = sanitizeName(Character.toLowerCase(nameInCamelCase.charAt(0)) + nameInCamelCase.substring(1)); + } else { + nameInCamelCase = sanitizeName(nameInCamelCase); + } + property.nameInCamelCase = nameInCamelCase; + return property; + } + + /** + * Output the Getter name for boolean property, e.g. isActive + * + * @param name the name of the property + * @return getter name based on naming convention + */ + public String toBooleanGetter(String name) { + return "is" + getterAndSetterCapitalize(name); + } +} diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppRestClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppRestClientCodegen.java new file mode 100644 index 00000000000..b0587cc2d29 --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppRestClientCodegen.java @@ -0,0 +1,429 @@ +package org.openapitools.codegen.languages; + +import static com.google.common.base.Strings.isNullOrEmpty; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Multimap; + +import org.apache.commons.lang3.StringUtils; +import org.openapitools.codegen.CliOption; +import org.openapitools.codegen.CodegenConstants; +import org.openapitools.codegen.CodegenModel; +import org.openapitools.codegen.CodegenOperation; +import org.openapitools.codegen.CodegenParameter; +import org.openapitools.codegen.CodegenProperty; +import org.openapitools.codegen.CodegenType; +import org.openapitools.codegen.SupportingFile; +import org.openapitools.codegen.utils.ModelUtils; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.media.*; +import io.swagger.v3.oas.models.responses.ApiResponse; +import io.swagger.v3.parser.util.SchemaTypeUtil; + + +public class CppRestClientCodegen extends AbstractCppCodegen { + + public static final String DECLSPEC = "declspec"; + public static final String DEFAULT_INCLUDE = "defaultInclude"; + + protected String packageVersion = "1.0.0"; + protected String declspec = ""; + protected String defaultInclude = ""; + + private final Set parentModels = new HashSet<>(); + private final Multimap childrenByParent = ArrayListMultimap.create(); + + /** + * Configures the type of generator. + * + * @return the CodegenType for this generator + * @see org.openapitools.codegen.CodegenType + */ + public CodegenType getTag() { + return CodegenType.CLIENT; + } + + /** + * 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 + */ + public String getName() { + return "cpprest"; + } + + /** + * Returns human-friendly help for the generator. Provide the consumer with + * help tips, parameters here + * + * @return A string value for the help message + */ + public String getHelp() { + return "Generates a C++ API client with C++ REST SDK (https://github.com/Microsoft/cpprestsdk)."; + } + + public CppRestClientCodegen() { + super(); + + apiPackage = "io.swagger.client.api"; + modelPackage = "io.swagger.client.model"; + + modelTemplateFiles.put("model-header.mustache", ".h"); + modelTemplateFiles.put("model-source.mustache", ".cpp"); + + apiTemplateFiles.put("api-header.mustache", ".h"); + apiTemplateFiles.put("api-source.mustache", ".cpp"); + + embeddedTemplateDir = templateDir = "cpprest"; + + cliOptions.clear(); + + // CLI options + addOption(CodegenConstants.MODEL_PACKAGE, "C++ namespace for models (convention: name.space.model).", + this.modelPackage); + addOption(CodegenConstants.API_PACKAGE, "C++ namespace for apis (convention: name.space.api).", + this.apiPackage); + addOption(CodegenConstants.PACKAGE_VERSION, "C++ package version.", this.packageVersion); + addOption(DECLSPEC, "C++ preprocessor to place before the class name for handling dllexport/dllimport.", + this.declspec); + addOption(DEFAULT_INCLUDE, + "The default include statement that should be placed in all headers for including things like the declspec (convention: #include \"Commons.h\" ", + this.defaultInclude); + + supportingFiles.add(new SupportingFile("modelbase-header.mustache", "", "ModelBase.h")); + supportingFiles.add(new SupportingFile("modelbase-source.mustache", "", "ModelBase.cpp")); + supportingFiles.add(new SupportingFile("object-header.mustache", "", "Object.h")); + supportingFiles.add(new SupportingFile("object-source.mustache", "", "Object.cpp")); + supportingFiles.add(new SupportingFile("apiclient-header.mustache", "", "ApiClient.h")); + supportingFiles.add(new SupportingFile("apiclient-source.mustache", "", "ApiClient.cpp")); + supportingFiles.add(new SupportingFile("apiconfiguration-header.mustache", "", "ApiConfiguration.h")); + supportingFiles.add(new SupportingFile("apiconfiguration-source.mustache", "", "ApiConfiguration.cpp")); + supportingFiles.add(new SupportingFile("apiexception-header.mustache", "", "ApiException.h")); + supportingFiles.add(new SupportingFile("apiexception-source.mustache", "", "ApiException.cpp")); + supportingFiles.add(new SupportingFile("ihttpbody-header.mustache", "", "IHttpBody.h")); + supportingFiles.add(new SupportingFile("jsonbody-header.mustache", "", "JsonBody.h")); + supportingFiles.add(new SupportingFile("jsonbody-source.mustache", "", "JsonBody.cpp")); + supportingFiles.add(new SupportingFile("httpcontent-header.mustache", "", "HttpContent.h")); + supportingFiles.add(new SupportingFile("httpcontent-source.mustache", "", "HttpContent.cpp")); + supportingFiles.add(new SupportingFile("multipart-header.mustache", "", "MultipartFormData.h")); + supportingFiles.add(new SupportingFile("multipart-source.mustache", "", "MultipartFormData.cpp")); + supportingFiles.add(new SupportingFile("gitignore.mustache", "", ".gitignore")); + supportingFiles.add(new SupportingFile("git_push.sh.mustache", "", "git_push.sh")); + supportingFiles.add(new SupportingFile("cmake-lists.mustache", "", "CMakeLists.txt")); + + languageSpecificPrimitives = new HashSet( + Arrays.asList("int", "char", "bool", "long", "float", "double", "int32_t", "int64_t")); + + typeMapping = new HashMap(); + typeMapping.put("date", "utility::datetime"); + typeMapping.put("DateTime", "utility::datetime"); + typeMapping.put("string", "utility::string_t"); + typeMapping.put("integer", "int32_t"); + typeMapping.put("long", "int64_t"); + typeMapping.put("boolean", "bool"); + typeMapping.put("array", "std::vector"); + typeMapping.put("map", "std::map"); + typeMapping.put("file", "HttpContent"); + typeMapping.put("object", "Object"); + typeMapping.put("binary", "std::string"); + typeMapping.put("number", "double"); + typeMapping.put("UUID", "utility::string_t"); + + super.importMapping = new HashMap(); + importMapping.put("std::vector", "#include "); + importMapping.put("std::map", "#include "); + importMapping.put("std::string", "#include "); + importMapping.put("HttpContent", "#include \"HttpContent.h\""); + importMapping.put("Object", "#include \"Object.h\""); + importMapping.put("utility::string_t", "#include "); + importMapping.put("utility::datetime", "#include "); + } + + @Override + public void processOpts() { + super.processOpts(); + + if (additionalProperties.containsKey(DECLSPEC)) { + declspec = additionalProperties.get(DECLSPEC).toString(); + } + + if (additionalProperties.containsKey(DEFAULT_INCLUDE)) { + defaultInclude = additionalProperties.get(DEFAULT_INCLUDE).toString(); + } + + additionalProperties.put("modelNamespaceDeclarations", modelPackage.split("\\.")); + additionalProperties.put("modelNamespace", modelPackage.replaceAll("\\.", "::")); + additionalProperties.put("modelHeaderGuardPrefix", modelPackage.replaceAll("\\.", "_").toUpperCase()); + additionalProperties.put("apiNamespaceDeclarations", apiPackage.split("\\.")); + additionalProperties.put("apiNamespace", apiPackage.replaceAll("\\.", "::")); + additionalProperties.put("apiHeaderGuardPrefix", apiPackage.replaceAll("\\.", "_").toUpperCase()); + additionalProperties.put("declspec", declspec); + additionalProperties.put("defaultInclude", defaultInclude); + } + + /** + * Location to write model files. You can use the modelPackage() as defined + * when the class is instantiated + */ + public String modelFileFolder() { + return outputFolder + "/model"; + } + + /** + * Location to write api files. You can use the apiPackage() as defined when + * the class is instantiated + */ + @Override + public String apiFileFolder() { + return outputFolder + "/api"; + } + + @Override + public String toModelImport(String name) { + if (importMapping.containsKey(name)) { + return importMapping.get(name); + } else { + return "#include \"" + name + ".h\""; + } + } + + @Override + public CodegenModel fromModel(String name, Schema model, Map allDefinitions) { + CodegenModel codegenModel = super.fromModel(name, model, allDefinitions); + + Set oldImports = codegenModel.imports; + codegenModel.imports = new HashSet(); + for (String imp : oldImports) { + String newImp = toModelImport(imp); + if (!newImp.isEmpty()) { + codegenModel.imports.add(newImp); + } + } + + return codegenModel; + } + + @Override + public CodegenOperation fromOperation(String path, String httpMethod, Operation operation, + Map schema, OpenAPI openAPI) { + CodegenOperation op = super.fromOperation(path, httpMethod, operation, schema, openAPI); + + if (operation.getResponses() != null && !operation.getResponses().isEmpty()) { + ApiResponse methodResponse = findMethodResponse(operation.getResponses()); + + if (methodResponse != null) { + Schema response = getSchemaFromResponse(methodResponse); + if (response != null) { + CodegenProperty cm = fromProperty("response", response); + op.vendorExtensions.put("x-codegen-response", cm); + if (cm.datatype == "HttpContent") { + op.vendorExtensions.put("x-codegen-response-ishttpcontent", true); + } + } + } + } + + return op; + } + + @Override + public void postProcessModelProperty(CodegenModel model, CodegenProperty property) { + if (isFileSchema(property)) { + property.vendorExtensions.put("x-codegen-file", true); + } + + if (!isNullOrEmpty(model.parent)) { + parentModels.add(model.parent); + if (!childrenByParent.containsEntry(model.parent, model)) { + childrenByParent.put(model.parent, model); + } + } + } + + protected boolean isFileSchema(CodegenProperty property) { + return property.baseType.equals("HttpContent"); + } + + @Override + public String toModelFilename(String name) { + return initialCaps(name); + } + + @Override + public String toApiFilename(String name) { + return initialCaps(name) + "Api"; + } + + /** + * Optional - type declaration. This is a String which is used by the + * templates to instantiate your types. There is typically special handling + * for different property types + * + * @return a string value used as the `dataType` field for model templates, + * `returnType` for api templates + */ + @Override + public String getTypeDeclaration(Schema p) { + String swaggerType = getSchemaType(p); + + if (p instanceof ArraySchema) { + ArraySchema ap = (ArraySchema) p; + Schema inner = ap.getItems(); + return getSchemaType(p) + "<" + getTypeDeclaration(inner) + ">"; + } + if (p instanceof MapSchema) { + MapSchema mp = (MapSchema) p; + Schema inner = (Schema) mp.getAdditionalProperties(); + return getSchemaType(p) + ""; + } + if (p instanceof StringSchema || p instanceof DateSchema + || p instanceof DateTimeSchema || p instanceof FileSchema + || languageSpecificPrimitives.contains(swaggerType)) { + return toModelName(swaggerType); + } + + return "std::shared_ptr<" + swaggerType + ">"; + } + + @Override + public String toDefaultValue(Schema p) { + if (p instanceof StringSchema) { + return "utility::conversions::to_string_t(\"\")"; + } else if (p instanceof BooleanSchema) { + return "false"; + } else if (p instanceof DateSchema) { + return "utility::datetime()"; + } else if (p instanceof DateTimeSchema) { + return "utility::datetime()"; + } else if (p instanceof NumberSchema) { + if(SchemaTypeUtil.FLOAT_FORMAT.equals(p.getFormat())) { + return "0.0f"; + } + return "0.0"; + } else if (p instanceof IntegerSchema) { + if(SchemaTypeUtil.INTEGER64_FORMAT.equals(p.getFormat())) { + return "0L"; + } + return "0"; + } else if (p instanceof MapSchema) { + MapSchema ap = (MapSchema) p; + String inner = getSchemaType((Schema) ap.getAdditionalProperties()); + return "std::map()"; + } else if (p instanceof ArraySchema) { + ArraySchema ap = (ArraySchema) p; + String inner = getSchemaType(ap.getItems()); + if (!languageSpecificPrimitives.contains(inner)) { + inner = "std::shared_ptr<" + inner + ">"; + } + return "std::vector<" + inner + ">()"; + } else if (!StringUtils.isEmpty(p.get$ref())) { + return "new " + toModelName(p.get$ref()) + "()"; + } + return "nullptr"; + } + + @Override + public void postProcessParameter(CodegenParameter parameter) { + super.postProcessParameter(parameter); + + boolean isPrimitiveType = parameter.isPrimitiveType == Boolean.TRUE; + boolean isListContainer = parameter.isListContainer == Boolean.TRUE; + boolean isString = parameter.isString == Boolean.TRUE; + + if (!isPrimitiveType && !isListContainer && !isString && !parameter.dataType.startsWith("std::shared_ptr")) { + parameter.dataType = "std::shared_ptr<" + parameter.dataType + ">"; + } + } + + /** + * Optional - swagger type conversion. This is used to map swagger types in + * a `Schema` into either language specific types via `typeMapping` or + * into complex models if there is not a mapping. + * + * @return a string value of the type or complex model for this property + * @see io.swagger.models.properties.Schema + */ + @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 toModelName(String type) { + if (typeMapping.keySet().contains(type) || typeMapping.values().contains(type) + || importMapping.values().contains(type) || defaultIncludes.contains(type) + || languageSpecificPrimitives.contains(type)) { + return type; + } else { + return Character.toUpperCase(type.charAt(0)) + type.substring(1); + } + } + + @Override + public String toApiName(String type) { + return Character.toUpperCase(type.charAt(0)) + type.substring(1) + "Api"; + } + + @Override + public String escapeQuotationMark(String input) { + // remove " to avoid code injection + return input.replace("\"", ""); + } + + @Override + public String escapeUnsafeCharacters(String input) { + return input.replace("*/", "*_/").replace("/*", "/_*"); + } + + @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 childSchema : child.vars) { + childPropertiesByName.put(childSchema.name, childSchema); + } + for (final CodegenProperty parentSchema : parent.vars) { + final CodegenProperty duplicatedByParent = childPropertiesByName.get(parentSchema.name); + if (duplicatedByParent != null) { + duplicatedByParent.isInherited = 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 15403282181..890c7b40d18 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 @@ -4,6 +4,7 @@ org.openapitools.codegen.languages.AkkaScalaClientCodegen org.openapitools.codegen.languages.BashClientCodegen org.openapitools.codegen.languages.ClojureClientCodegen org.openapitools.codegen.languages.ConfluenceWikiCodegen +org.openapitools.codegen.languages.CppRestClientCodegen org.openapitools.codegen.languages.DartClientCodegen org.openapitools.codegen.languages.ElixirClientCodegen org.openapitools.codegen.languages.HaskellServantCodegen From ff50ed187c198ce133700fa6fd3088ba92d1d838 Mon Sep 17 00:00:00 2001 From: wing328 Date: Wed, 28 Mar 2018 00:48:46 +0800 Subject: [PATCH 09/29] add cpp client generator --- .../languages/CppRestClientCodegen.java | 20 +- .../languages/Qt5CPPClientCodegen.java | 425 ++++++++++++++++++ .../org.openapitools.codegen.CodegenConfig | 1 + .../main/resources/qt5cpp/Project.mustache | 43 ++ .../qt5cpp/QObjectWrapper.h.mustache | 28 ++ 5 files changed, 507 insertions(+), 10 deletions(-) create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Qt5CPPClientCodegen.java create mode 100644 modules/openapi-generator/src/main/resources/qt5cpp/Project.mustache create mode 100644 modules/openapi-generator/src/main/resources/qt5cpp/QObjectWrapper.h.mustache diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppRestClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppRestClientCodegen.java index b0587cc2d29..96b6029df26 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppRestClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppRestClientCodegen.java @@ -273,7 +273,7 @@ public class CppRestClientCodegen extends AbstractCppCodegen { */ @Override public String getTypeDeclaration(Schema p) { - String swaggerType = getSchemaType(p); + String openAPIType = getSchemaType(p); if (p instanceof ArraySchema) { ArraySchema ap = (ArraySchema) p; @@ -287,11 +287,11 @@ public class CppRestClientCodegen extends AbstractCppCodegen { } if (p instanceof StringSchema || p instanceof DateSchema || p instanceof DateTimeSchema || p instanceof FileSchema - || languageSpecificPrimitives.contains(swaggerType)) { - return toModelName(swaggerType); + || languageSpecificPrimitives.contains(openAPIType)) { + return toModelName(openAPIType); } - return "std::shared_ptr<" + swaggerType + ">"; + return "std::shared_ptr<" + openAPIType + ">"; } @Override @@ -305,12 +305,12 @@ public class CppRestClientCodegen extends AbstractCppCodegen { } else if (p instanceof DateTimeSchema) { return "utility::datetime()"; } else if (p instanceof NumberSchema) { - if(SchemaTypeUtil.FLOAT_FORMAT.equals(p.getFormat())) { + if (SchemaTypeUtil.FLOAT_FORMAT.equals(p.getFormat())) { return "0.0f"; } return "0.0"; } else if (p instanceof IntegerSchema) { - if(SchemaTypeUtil.INTEGER64_FORMAT.equals(p.getFormat())) { + if (SchemaTypeUtil.INTEGER64_FORMAT.equals(p.getFormat())) { return "0L"; } return "0"; @@ -354,14 +354,14 @@ public class CppRestClientCodegen extends AbstractCppCodegen { */ @Override public String getSchemaType(Schema p) { - String swaggerType = super.getSchemaType(p); + String openAPIType = super.getSchemaType(p); String type = null; - if (typeMapping.containsKey(swaggerType)) { - type = typeMapping.get(swaggerType); + if (typeMapping.containsKey(openAPIType)) { + type = typeMapping.get(openAPIType); if (languageSpecificPrimitives.contains(type)) return toModelName(type); } else - type = swaggerType; + type = openAPIType; return toModelName(type); } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Qt5CPPClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Qt5CPPClientCodegen.java new file mode 100644 index 00000000000..e23bc88231f --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Qt5CPPClientCodegen.java @@ -0,0 +1,425 @@ +package org.openapitools.codegen.languages; + +import org.apache.commons.lang3.StringUtils; +import org.openapitools.codegen.*; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.media.*; +import io.swagger.v3.oas.models.responses.ApiResponse; +import io.swagger.v3.parser.util.SchemaTypeUtil; + +import java.io.File; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class Qt5CPPClientCodegen extends AbstractCppCodegen implements CodegenConfig { + public static final String CPP_NAMESPACE = "cppNamespace"; + public static final String CPP_NAMESPACE_DESC = "C++ namespace (convention: name::space::for::api)."; + public static final String OPTIONAL_PROJECT_FILE_DESC = "Generate client.pri."; + + protected final String PREFIX = "SWG"; + protected Set foundationClasses = new HashSet(); + // source folder where to write the files + protected String sourceFolder = "client"; + protected String apiVersion = "1.0.0"; + protected Map namespaces = new HashMap(); + protected Set systemIncludes = new HashSet(); + protected String cppNamespace = "Swagger"; + protected boolean optionalProjectFileFlag = true; + + public Qt5CPPClientCodegen() { + super(); + + // set the output folder here + outputFolder = "generated-code/qt5cpp"; + + // set modelNamePrefix as default for QT5CPP + if (modelNamePrefix == "") { + modelNamePrefix = PREFIX; + } + + /* + * 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-header.mustache", + ".h"); + + modelTemplateFiles.put( + "model-body.mustache", + ".cpp"); + + /* + * 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( + "api-header.mustache", // the template to use + ".h"); // the extension for each file to write + + apiTemplateFiles.put( + "api-body.mustache", // the template to use + ".cpp"); // the extension for each file to write + + /* + * Template Location. This is the location which templates will be read from. The generator + * will use the resource stream to attempt to read the templates. + */ + embeddedTemplateDir = templateDir = "qt5cpp"; + + // CLI options + addOption(CPP_NAMESPACE, CPP_NAMESPACE_DESC, this.cppNamespace); + addSwitch(CodegenConstants.OPTIONAL_PROJECT_FILE, OPTIONAL_PROJECT_FILE_DESC, this.optionalProjectFileFlag); + + /* + * 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("prefix", PREFIX); + + // Write defaults namespace in properties so that it can be accessible in templates. + // At this point command line has not been parsed so if value is given + // in command line it will superseed this content + additionalProperties.put("cppNamespace", cppNamespace); + + /* + * Language Specific Primitives. These types will not trigger imports by + * the client generator + */ + languageSpecificPrimitives = new HashSet( + Arrays.asList( + "bool", + "qint32", + "qint64", + "float", + "double") + ); + + supportingFiles.add(new SupportingFile("helpers-header.mustache", sourceFolder, PREFIX + "Helpers.h")); + supportingFiles.add(new SupportingFile("helpers-body.mustache", sourceFolder, PREFIX + "Helpers.cpp")); + supportingFiles.add(new SupportingFile("HttpRequest.h.mustache", sourceFolder, PREFIX + "HttpRequest.h")); + supportingFiles.add(new SupportingFile("HttpRequest.cpp.mustache", sourceFolder, PREFIX + "HttpRequest.cpp")); + supportingFiles.add(new SupportingFile("modelFactory.mustache", sourceFolder, PREFIX + "ModelFactory.h")); + supportingFiles.add(new SupportingFile("object.mustache", sourceFolder, PREFIX + "Object.h")); + supportingFiles.add(new SupportingFile("QObjectWrapper.h.mustache", sourceFolder, PREFIX + "QObjectWrapper.h")); + if (optionalProjectFileFlag) { + supportingFiles.add(new SupportingFile("Project.mustache", sourceFolder, "client.pri")); + } + + super.typeMapping = new HashMap(); + + typeMapping.put("date", "QDate"); + typeMapping.put("DateTime", "QDateTime"); + typeMapping.put("string", "QString"); + typeMapping.put("integer", "qint32"); + typeMapping.put("long", "qint64"); + typeMapping.put("boolean", "bool"); + typeMapping.put("array", "QList"); + typeMapping.put("map", "QMap"); + typeMapping.put("file", "SWGHttpRequestInputFileElement"); + typeMapping.put("object", PREFIX + "Object"); + //TODO binary should be mapped to byte array + // mapped to String as a workaround + typeMapping.put("binary", "QString"); + typeMapping.put("ByteArray", "QByteArray"); + // UUID support - possible enhancement : use QUuid instead of QString. + // beware though that Serialisation/deserialisation of QUuid does not + // come out of the box and will need to be sorted out (at least imply + // modifications on multiple templates) + typeMapping.put("UUID", "QString"); + + importMapping = new HashMap(); + + importMapping.put("SWGHttpRequestInputFileElement", "#include \"" + PREFIX + "HttpRequest.h\""); + + namespaces = new HashMap(); + + foundationClasses.add("QString"); + + systemIncludes.add("QString"); + systemIncludes.add("QList"); + systemIncludes.add("QMap"); + systemIncludes.add("QDate"); + systemIncludes.add("QDateTime"); + systemIncludes.add("QByteArray"); + } + + @Override + public void processOpts() { + super.processOpts(); + + if (additionalProperties.containsKey("cppNamespace")) { + cppNamespace = (String) additionalProperties.get("cppNamespace"); + } + + additionalProperties.put("cppNamespaceDeclarations", cppNamespace.split("\\::")); + if (additionalProperties.containsKey("modelNamePrefix")) { + supportingFiles.clear(); + supportingFiles.add(new SupportingFile("helpers-header.mustache", sourceFolder, modelNamePrefix + "Helpers.h")); + supportingFiles.add(new SupportingFile("helpers-body.mustache", sourceFolder, modelNamePrefix + "Helpers.cpp")); + supportingFiles.add(new SupportingFile("HttpRequest.h.mustache", sourceFolder, modelNamePrefix + "HttpRequest.h")); + supportingFiles.add(new SupportingFile("HttpRequest.cpp.mustache", sourceFolder, modelNamePrefix + "HttpRequest.cpp")); + supportingFiles.add(new SupportingFile("modelFactory.mustache", sourceFolder, modelNamePrefix + "ModelFactory.h")); + supportingFiles.add(new SupportingFile("object.mustache", sourceFolder, modelNamePrefix + "Object.h")); + supportingFiles.add(new SupportingFile("QObjectWrapper.h.mustache", sourceFolder, modelNamePrefix + "QObjectWrapper.h")); + + typeMapping.put("object", modelNamePrefix + "Object"); + typeMapping.put("file", modelNamePrefix + "HttpRequestInputFileElement"); + importMapping.put("SWGHttpRequestInputFileElement", "#include \"" + modelNamePrefix + "HttpRequest.h\""); + additionalProperties().put("prefix", modelNamePrefix); + } + + if (additionalProperties.containsKey(CodegenConstants.OPTIONAL_PROJECT_FILE)) { + setOptionalProjectFileFlag(convertPropertyToBooleanAndWriteBack(CodegenConstants.OPTIONAL_PROJECT_FILE)); + } else { + additionalProperties.put(CodegenConstants.OPTIONAL_PROJECT_FILE, optionalProjectFileFlag); + } + } + + /** + * Configures the type of generator. + * + * @return the CodegenType for this generator + * @see org.openapitools.codegen.CodegenType + */ + @Override + public CodegenType getTag() { + return CodegenType.CLIENT; + } + + /** + * 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 "qt5cpp"; + } + + /** + * 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 Qt5 C++ client library."; + } + + @Override + public String toModelImport(String name) { + if (namespaces.containsKey(name)) { + return "using " + namespaces.get(name) + ";"; + } else if (systemIncludes.contains(name)) { + return "#include <" + name + ">"; + } + + String folder = modelPackage().replace("::", File.separator); + if (!folder.isEmpty()) + folder += File.separator; + + return "#include \"" + folder + name + ".h\""; + } + + /** + * 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) { + if (this.reservedWordsMappings().containsKey(name)) { + return this.reservedWordsMappings().get(name); + } + return "_" + name; + } + + /** + * Location to write model files. You can use the modelPackage() as defined when the class is + * instantiated + */ + @Override + public String modelFileFolder() { + return outputFolder + "/" + sourceFolder + "/" + modelPackage().replace("::", File.separator); + } + + /** + * Location to write api files. You can use the apiPackage() as defined when the class is + * instantiated + */ + @Override + public String apiFileFolder() { + return outputFolder + "/" + sourceFolder + "/" + apiPackage().replace("::", File.separator); + } + + @Override + public String toModelFilename(String name) { + return modelNamePrefix + initialCaps(name); + } + + @Override + public String toApiFilename(String name) { + return modelNamePrefix + initialCaps(name) + "Api"; + } + + /** + * Optional - type declaration. This is a String which is used by the templates to instantiate your + * types. There is typically special handling for different property types + * + * @return a string value used as the `dataType` field for model templates, `returnType` for api templates + */ + @Override + public String getTypeDeclaration(Schema p) { + String openAPIType = getSchemaType(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) + "*"; + } + if (foundationClasses.contains(openAPIType)) { + return openAPIType + "*"; + } else if (languageSpecificPrimitives.contains(openAPIType)) { + return toModelName(openAPIType); + } else { + return openAPIType + "*"; + } + } + + + @Override + public String toDefaultValue(Schema p) { + if (p instanceof StringSchema) { + return "new QString(\"\")"; + } else if (p instanceof BooleanSchema) { + return "false"; + } else if (p instanceof DateSchema) { + return "NULL"; + } else if (p instanceof DateTimeSchema) { + return "NULL"; + } else if (p instanceof NumberSchema) { + if (SchemaTypeUtil.FLOAT_FORMAT.equals(p.getFormat())) { + return "0.0f"; + } + return "0.0"; + } else if (p instanceof IntegerSchema) { + if (SchemaTypeUtil.INTEGER64_FORMAT.equals(p.getFormat())) { + return "0L"; + } + return "0"; + } else if (p instanceof MapSchema) { + MapSchema mp = (MapSchema) p; + Schema inner = (Schema) mp.getAdditionalProperties(); + return "new QMap()"; + } else if (p instanceof ArraySchema) { + ArraySchema ap = (ArraySchema) p; + Schema inner = ap.getItems(); + return "new QList<" + getTypeDeclaration(inner) + ">()"; + } + // else + if (!StringUtils.isEmpty(p.get$ref())) { + return "new " + toModelName(p.get$ref()) + "()"; + } + return "NULL"; + } + + /** + * Optional - swagger type conversion. This is used to map swagger types in a `Schema` into + * either language specific types via `typeMapping` or into complex models if there is not a mapping. + * + * @return a string value of the type or complex model for this property + */ + @Override + public String getSchemaType(Schema p) { + String openAPIType = super.getSchemaType(p); + String type = null; + if (typeMapping.containsKey(openAPIType)) { + type = typeMapping.get(openAPIType); + if (languageSpecificPrimitives.contains(type)) { + return toModelName(type); + } + if (foundationClasses.contains(type)) { + return type; + } + } else { + type = openAPIType; + } + return toModelName(type); + } + + @Override + public String toModelName(String type) { + if (typeMapping.keySet().contains(type) || + typeMapping.values().contains(type) || + importMapping.values().contains(type) || + defaultIncludes.contains(type) || + languageSpecificPrimitives.contains(type)) { + return type; + } else { + return modelNamePrefix + Character.toUpperCase(type.charAt(0)) + type.substring(1); + } + } + + @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 it's all uppper case, convert to lower case + if (name.matches("^[A-Z_]*$")) { + name = name.toLowerCase(); + } + + // camelize (lower first character) the variable name + // petId => pet_id + name = underscore(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) { + return toVarName(name); + } + + @Override + public String toApiName(String type) { + return modelNamePrefix + Character.toUpperCase(type.charAt(0)) + type.substring(1) + "Api"; + } + + @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 void setOptionalProjectFileFlag(boolean flag) { + this.optionalProjectFileFlag = flag; + } +} 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 890c7b40d18..c37c3c67f60 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 @@ -13,6 +13,7 @@ org.openapitools.codegen.languages.ObjcClientCodegen org.openapitools.codegen.languages.PhpClientCodegen org.openapitools.codegen.languages.PowerShellClientCodegen org.openapitools.codegen.languages.PythonClientCodegen +org.openapitools.codegen.languages.Qt5CPPClientCodegen org.openapitools.codegen.languages.RClientCodegen org.openapitools.codegen.languages.Rails5ServerCodegen org.openapitools.codegen.languages.RubyClientCodegen diff --git a/modules/openapi-generator/src/main/resources/qt5cpp/Project.mustache b/modules/openapi-generator/src/main/resources/qt5cpp/Project.mustache new file mode 100644 index 00000000000..dac7276b98c --- /dev/null +++ b/modules/openapi-generator/src/main/resources/qt5cpp/Project.mustache @@ -0,0 +1,43 @@ +QT += network + +HEADERS += \ +# Models +{{#models}} +{{#model}} + $${PWD}/{{classname}}.h \ +{{/model}} +{{/models}} +# APIs +{{#apiInfo}} +{{#apis}} +{{#operations}} + $${PWD}/{{classname}}.h \ +{{/operations}} +{{/apis}} +{{/apiInfo}} +# Others + $${PWD}/{{prefix}}Helpers.h \ + $${PWD}/{{prefix}}HttpRequest.h \ + $${PWD}/{{prefix}}ModelFactory.h \ + $${PWD}/{{prefix}}Object.h \ + $${PWD}/{{prefix}}QObjectWrapper.h + +SOURCES += \ +# Models +{{#models}} +{{#model}} + $${PWD}/{{classname}}.cpp \ +{{/model}} +{{/models}} +# APIs +{{#apiInfo}} +{{#apis}} +{{#operations}} + $${PWD}/{{classname}}.cpp \ +{{/operations}} +{{/apis}} +{{/apiInfo}} +# Others + $${PWD}/{{prefix}}Helpers.cpp \ + $${PWD}/{{prefix}}HttpRequest.cpp + diff --git a/modules/openapi-generator/src/main/resources/qt5cpp/QObjectWrapper.h.mustache b/modules/openapi-generator/src/main/resources/qt5cpp/QObjectWrapper.h.mustache new file mode 100644 index 00000000000..efd71407777 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/qt5cpp/QObjectWrapper.h.mustache @@ -0,0 +1,28 @@ +{{>licenseInfo}} +#ifndef {{prefix}}_QOBJECT_WRAPPER_H +#define {{prefix}}_QOBJECT_WRAPPER_H + +#include + +{{#cppNamespaceDeclarations}} +namespace {{this}} { +{{/cppNamespaceDeclarations}} + + template + class {{prefix}}QObjectWrapper : public QObject { + public: + {{prefix}}QObjectWrapper(ObjectPtrT ptr){ + data = ptr; + } + ~{{prefix}}QObjectWrapper(){ + delete data; + } + private : + ObjectPtrT data; + }; + +{{#cppNamespaceDeclarations}} +} +{{/cppNamespaceDeclarations}} + +#endif // {{prefix}}_QOBJECT_WRAPPER_H \ No newline at end of file From 696b6a09307a60fcbbb6e54119cdc843ce14e000 Mon Sep 17 00:00:00 2001 From: Jim Schubert Date: Tue, 27 Mar 2018 22:11:49 -0400 Subject: [PATCH 10/29] Add missing generator cliOption --- .../org/openapitools/codegen/languages/GoClientCodegen.java | 6 ++++++ .../java/org/openapitools/codegen/AbstractOptionsTest.java | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/GoClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/GoClientCodegen.java index 353a0a3d7ea..30068febef6 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/GoClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/GoClientCodegen.java @@ -51,6 +51,12 @@ public class GoClientCodegen extends AbstractGoCodegen { .defaultValue("1.0.0")); cliOptions.add(CliOption.newBoolean(WITH_XML, "whether to include support for application/xml content type and include XML annotations in the model (works with libraries that provide support for JSON and XML)")); + // option to change the order of form/body parameter + cliOptions.add(CliOption.newBoolean( + CodegenConstants.PREPEND_FORM_OR_BODY_PARAMETERS, + CodegenConstants.PREPEND_FORM_OR_BODY_PARAMETERS_DESC) + .defaultValue(Boolean.FALSE.toString())); + } @Override diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/AbstractOptionsTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/AbstractOptionsTest.java index ea1c9c2c5b3..10a2aae7212 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/AbstractOptionsTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/AbstractOptionsTest.java @@ -46,7 +46,7 @@ public abstract class AbstractOptionsTest { final Set undocumented = new HashSet(testOptions); undocumented.removeAll(cliOptions); if (!undocumented.isEmpty()) { - Assert.fail(String.format("These options weren't documented: %s.", StringUtils.join(undocumented, ", "))); + Assert.fail(String.format("These options weren't documented: %s. Are you expecting base options and calling cliOptions.clear()?", StringUtils.join(undocumented, ", "))); } } From aa697b15b71e065ec5c1c0043d0424f373ab4e17 Mon Sep 17 00:00:00 2001 From: wing328 Date: Wed, 28 Mar 2018 10:52:01 +0800 Subject: [PATCH 11/29] add tizen generator --- .../codegen/languages/TizenClientCodegen.java | 282 ++++++++++++++++++ .../org.openapitools.codegen.CodegenConfig | 1 + 2 files changed, 283 insertions(+) create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TizenClientCodegen.java diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TizenClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TizenClientCodegen.java new file mode 100644 index 00000000000..1e3b44d6cd9 --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TizenClientCodegen.java @@ -0,0 +1,282 @@ +package org.openapitools.codegen.languages; + +import org.apache.commons.lang3.StringUtils; +import org.openapitools.codegen.CodegenConfig; +import org.openapitools.codegen.CodegenType; +import org.openapitools.codegen.DefaultCodegen; +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 io.swagger.v3.oas.models.responses.ApiResponse; +import io.swagger.v3.parser.util.SchemaTypeUtil; + +import java.io.File; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + + +public class TizenClientCodegen extends DefaultCodegen implements CodegenConfig { + protected static String PREFIX = "ArtikCloud"; + protected String sourceFolder = "src"; + protected String documentationFolder = "doc"; + + public TizenClientCodegen() { + super(); + outputFolder = ""; + modelTemplateFiles.put("model-header.mustache", ".h"); + modelTemplateFiles.put("model-body.mustache", ".cpp"); + apiTemplateFiles.put("api-header.mustache", ".h"); + apiTemplateFiles.put("api-body.mustache", ".cpp"); + embeddedTemplateDir = templateDir = "tizen"; + modelPackage = ""; + + defaultIncludes = new HashSet( + Arrays.asList( + "bool", + "int", + "long long", + "double", + "float") + ); + languageSpecificPrimitives = new HashSet( + Arrays.asList( + "bool", + "int", + "long long", + "double", + "float", + "std::string") + ); + + additionalProperties().put("prefix", PREFIX); + + setReservedWordsLowerCase( + Arrays.asList( + "alignas", "alignof", "and", "and_eq", "asm", "atomic_cancel", "atomic_commit", "atomic_noexcept", + "auto", "bitand", "bitor", "bool", "break", "case", "catch", "char", "char16_t", "char32_t", + "class", "compl", "concept", "const", "constexpr", "const_cast", "continue", "decltype", "default", + "delete", "do", "double", "dynamic_cast", "else", "enum", "explicit", "export", "extern", "false", + "float", "for", "friend", "goto", "if", "inline", "int", "import", "long", "module", "mutable", + "namespace", "new", "noexcept", "not", "not_eq", "nullptr", "operator", "or", "or_eq", "private", + "protected", "public", "register", "reinterpret_cast", "requires", "return", "short", "signed", + "sizeof", "static", "static_assert", "static_cast", "struct", "switch", "synchronized", "template", + "this", "thread_local", "throw", "true", "try", "typedef", "typeid", "typename", "union", + "unsigned", "using", "virtual", "void", "volatile", "wchar_t", "while", "xor", "xor_eq" + )); + + super.typeMapping = new HashMap(); + + //typeMapping.put("Date", "DateTime"); + //typeMapping.put("DateTime", "DateTime"); + typeMapping.put("string", "std::string"); + typeMapping.put("integer", "int"); + typeMapping.put("float", "float"); + typeMapping.put("long", "long long"); + typeMapping.put("boolean", "bool"); + typeMapping.put("double", "double"); + typeMapping.put("array", "std::list"); + typeMapping.put("map", "std::map"); + typeMapping.put("number", "long long"); + typeMapping.put("object", "std::string"); + typeMapping.put("binary", "std::string"); + typeMapping.put("password", "std::string"); + //TODO:Maybe use better formats for dateTime? + typeMapping.put("file", "std::string"); + typeMapping.put("DateTime", "std::string"); + typeMapping.put("Date", "std::string"); + typeMapping.put("UUID", "std::string"); + + importMapping = new HashMap(); + + supportingFiles.clear(); + supportingFiles.add(new SupportingFile("helpers-header.mustache", sourceFolder, "Helpers.h")); + supportingFiles.add(new SupportingFile("helpers-body.mustache", sourceFolder, "Helpers.cpp")); + supportingFiles.add(new SupportingFile("netclient-header.mustache", sourceFolder, "NetClient.h")); + supportingFiles.add(new SupportingFile("netclient-body.mustache", sourceFolder, "NetClient.cpp")); + supportingFiles.add(new SupportingFile("object.mustache", sourceFolder, "Object.h")); + supportingFiles.add(new SupportingFile("requestinfo.mustache", sourceFolder, "RequestInfo.h")); + supportingFiles.add(new SupportingFile("error-header.mustache", sourceFolder, "Error.h")); + supportingFiles.add(new SupportingFile("error-body.mustache", sourceFolder, "Error.cpp")); + supportingFiles.add(new SupportingFile("Doxyfile.mustache", documentationFolder, "Doxyfile")); + supportingFiles.add(new SupportingFile("generateDocumentation.mustache", documentationFolder, "generateDocumentation.sh")); + supportingFiles.add(new SupportingFile("doc-readme.mustache", documentationFolder, "README.md")); + } + + @Override + public CodegenType getTag() { + return CodegenType.CLIENT; + } + + @Override + public String getName() { + return "tizen"; + } + + @Override + public String getHelp() { + return "Generates a Samsung Tizen C++ client library."; + } + + @Override + public String toInstantiationType(Schema p) { + if (p instanceof MapSchema) { + return instantiationTypes.get("map"); + } else if (p instanceof ArraySchema) { + return instantiationTypes.get("array"); + } else { + return null; + } + } + + @Override + public String getTypeDeclaration(String name) { + if (languageSpecificPrimitives.contains(name)) { + return name; + } else { + return name + ""; + } + } + + @Override + public String getSchemaType(Schema p) { + String openAPIType = super.getSchemaType(p); + String type = null; + if (typeMapping.containsKey(openAPIType)) { + type = typeMapping.get(openAPIType); + if (languageSpecificPrimitives.contains(type)) { + return toModelName(type); + } + } else { + type = openAPIType; + } + return toModelName(type); + } + + @Override + public String getTypeDeclaration(Schema p) { + String openAPIType = getSchemaType(p); + if (languageSpecificPrimitives.contains(openAPIType)) { + return toModelName(openAPIType); + } else { + return openAPIType + ""; + } + } + + @Override + public String toModelName(String type) { + if (typeMapping.keySet().contains(type) || + typeMapping.values().contains(type) || + importMapping.values().contains(type) || + defaultIncludes.contains(type) || + languageSpecificPrimitives.contains(type)) { + return type; + } else { + return Character.toUpperCase(type.charAt(0)) + type.substring(1); + } + } + + @Override + public String toModelImport(String name) { + if (name.equals("std::string")) { + return "#include "; + } else if (name.equals("std::map")) { + return "#include "; + } else if (name.equals("std::list")) { + return "#include "; + } + return "#include \"" + name + ".h\""; + } + + //Might not be needed + @Override + public String toDefaultValue(Schema p) { + if (p instanceof StringSchema) { + return "std::string()"; + } else if (p instanceof BooleanSchema) { + return "bool(false)"; + } else if (p instanceof NumberSchema) { + if (SchemaTypeUtil.FLOAT_FORMAT.equals(p.getFormat())) { + return "float(0)"; + } + return "double(0)"; + + } else if (p instanceof IntegerSchema) { + if (SchemaTypeUtil.INTEGER64_FORMAT.equals(p.getFormat())) { + return "long(0)"; + } + return "int(0)"; + } else if (p instanceof MapSchema) { + return "new std::map()"; + } else if (p instanceof ArraySchema) { + return "new std::list()"; + } else if (!StringUtils.isEmpty(p.get$ref())) { + return "new " + toModelName(p.get$ref()) + "()"; + } + return "null"; + } + + + @Override + public String apiFileFolder() { + return outputFolder + File.separator + sourceFolder; + } + + @Override + public String modelFileFolder() { + return outputFolder + File.separator + sourceFolder; + } + + @Override + public String toModelFilename(String name) { + return initialCaps(name); + } + + @Override + public String toApiName(String name) { + return initialCaps(name) + "Manager"; + } + + @Override + public String toApiFilename(String name) { + return initialCaps(name) + "Manager"; + } + + @Override + public String toVarName(String name) { + String paramName = name.replaceAll("[^a-zA-Z0-9_]", ""); + paramName = Character.toLowerCase(paramName.charAt(0)) + paramName.substring(1); + if (isReservedWord(paramName)) { + return escapeReservedWord(paramName); + } + return "" + paramName; + } + + @Override + public String escapeReservedWord(String name) { + if (this.reservedWordsMappings().containsKey(name)) { + return this.reservedWordsMappings().get(name); + } + return "_" + name; + } + + @Override + public String toOperationId(String operationId) { + // throw exception if method name is empty + if (operationId == "") { + throw new RuntimeException("Empty method name (operationId) not allowed"); + } + + // method name cannot use reserved keyword, e.g. return$ + if (isReservedWord(operationId)) { + operationId = escapeReservedWord(operationId); + } + + // add_pet_by_id => addPetById + return camelize(operationId, 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 c37c3c67f60..5952912e7f6 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 @@ -21,3 +21,4 @@ org.openapitools.codegen.languages.ScalaClientCodegen org.openapitools.codegen.languages.SlimFrameworkServerCodegen org.openapitools.codegen.languages.SilexServerCodegen org.openapitools.codegen.languages.SinatraServerCodegen +org.openapitools.codegen.languages.TizenClientCodegen From 16183cba71ab6956075837bac2cc7734804df7e4 Mon Sep 17 00:00:00 2001 From: wing328 Date: Wed, 28 Mar 2018 11:09:30 +0800 Subject: [PATCH 12/29] add kotlin client, server generator --- .../languages/AbstractKotlinCodegen.java | 534 ++++++++++++++++++ .../languages/KotlinClientCodegen.java | 117 ++++ .../languages/KotlinServerCodegen.java | 230 ++++++++ .../org.openapitools.codegen.CodegenConfig | 2 + 4 files changed, 883 insertions(+) create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractKotlinCodegen.java create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinClientCodegen.java create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinServerCodegen.java diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractKotlinCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractKotlinCodegen.java new file mode 100644 index 00000000000..7095ffad6ad --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractKotlinCodegen.java @@ -0,0 +1,534 @@ +package org.openapitools.codegen.languages; + +import org.openapitools.codegen.CliOption; +import org.openapitools.codegen.CodegenConfig; +import org.openapitools.codegen.CodegenConstants; +import org.openapitools.codegen.DefaultCodegen; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.media.*; +import io.swagger.v3.oas.models.responses.ApiResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; + +public abstract class AbstractKotlinCodegen extends DefaultCodegen implements CodegenConfig { + static Logger LOGGER = LoggerFactory.getLogger(AbstractKotlinCodegen.class); + + protected String artifactId; + protected String artifactVersion = "1.0.0"; + protected String groupId = "io.swagger"; + protected String packageName; + + protected String sourceFolder = "src/main/kotlin"; + + protected String apiDocPath = "docs/"; + protected String modelDocPath = "docs/"; + + protected CodegenConstants.ENUM_PROPERTY_NAMING_TYPE enumPropertyNaming = CodegenConstants.ENUM_PROPERTY_NAMING_TYPE.camelCase; + + public AbstractKotlinCodegen() { + super(); + supportsInheritance = true; + + languageSpecificPrimitives = new HashSet(Arrays.asList( + "kotlin.Byte", + "kotlin.Short", + "kotlin.Int", + "kotlin.Long", + "kotlin.Float", + "kotlin.Double", + "kotlin.Boolean", + "kotlin.Char", + "kotlin.String", + "kotlin.Array", + "kotlin.collections.List", + "kotlin.collections.Map", + "kotlin.collections.Set" + )); + + // this includes hard reserved words defined by https://github.com/JetBrains/kotlin/blob/master/core/descriptors/src/org/jetbrains/kotlin/renderer/KeywordStringsGenerated.java + // as well as keywords from https://kotlinlang.org/docs/reference/keyword-reference.html + reservedWords = new HashSet(Arrays.asList( + "abstract", + "annotation", + "as", + "break", + "case", + "catch", + "class", + "companion", + "const", + "constructor", + "continue", + "crossinline", + "data", + "delegate", + "do", + "else", + "enum", + "external", + "false", + "final", + "finally", + "for", + "fun", + "if", + "in", + "infix", + "init", + "inline", + "inner", + "interface", + "internal", + "is", + "it", + "lateinit", + "lazy", + "noinline", + "null", + "object", + "open", + "operator", + "out", + "override", + "package", + "private", + "protected", + "public", + "reified", + "return", + "sealed", + "super", + "suspend", + "tailrec", + "this", + "throw", + "true", + "try", + "typealias", + "typeof", + "val", + "var", + "vararg", + "when", + "while" + )); + + defaultIncludes = new HashSet(Arrays.asList( + "kotlin.Byte", + "kotlin.Short", + "kotlin.Int", + "kotlin.Long", + "kotlin.Float", + "kotlin.Double", + "kotlin.Boolean", + "kotlin.Char", + "kotlin.Array", + "kotlin.collections.List", + "kotlin.collections.Set", + "kotlin.collections.Map" + )); + + typeMapping = new HashMap(); + typeMapping.put("string", "kotlin.String"); + typeMapping.put("boolean", "kotlin.Boolean"); + typeMapping.put("integer", "kotlin.Int"); + typeMapping.put("float", "kotlin.Float"); + typeMapping.put("long", "kotlin.Long"); + typeMapping.put("double", "kotlin.Double"); + typeMapping.put("number", "java.math.BigDecimal"); + typeMapping.put("date-time", "java.time.LocalDateTime"); + typeMapping.put("date", "java.time.LocalDateTime"); + typeMapping.put("file", "java.io.File"); + typeMapping.put("array", "kotlin.Array"); + typeMapping.put("list", "kotlin.Array"); + typeMapping.put("map", "kotlin.collections.Map"); + typeMapping.put("object", "kotlin.Any"); + typeMapping.put("binary", "kotlin.Array"); + typeMapping.put("Date", "java.time.LocalDateTime"); + typeMapping.put("DateTime", "java.time.LocalDateTime"); + + instantiationTypes.put("array", "arrayOf"); + instantiationTypes.put("list", "arrayOf"); + instantiationTypes.put("map", "mapOf"); + + importMapping = new HashMap(); + importMapping.put("BigDecimal", "java.math.BigDecimal"); + importMapping.put("UUID", "java.util.UUID"); + importMapping.put("File", "java.io.File"); + importMapping.put("Date", "java.util.Date"); + importMapping.put("Timestamp", "java.sql.Timestamp"); + importMapping.put("DateTime", "java.time.LocalDateTime"); + importMapping.put("LocalDateTime", "java.time.LocalDateTime"); + importMapping.put("LocalDate", "java.time.LocalDate"); + importMapping.put("LocalTime", "java.time.LocalTime"); + + specialCharReplacements.put(";", "Semicolon"); + + cliOptions.clear(); + addOption(CodegenConstants.SOURCE_FOLDER, CodegenConstants.SOURCE_FOLDER_DESC, sourceFolder); + addOption(CodegenConstants.PACKAGE_NAME, "Generated artifact package name (e.g. io.swagger).", packageName); + addOption(CodegenConstants.GROUP_ID, "Generated artifact package's organization (i.e. maven groupId).", groupId); + addOption(CodegenConstants.ARTIFACT_ID, "Generated artifact id (name of jar).", artifactId); + addOption(CodegenConstants.ARTIFACT_VERSION, "Generated artifact's package version.", artifactVersion); + + CliOption enumPropertyNamingOpt = new CliOption(CodegenConstants.ENUM_PROPERTY_NAMING, CodegenConstants.ENUM_PROPERTY_NAMING_DESC); + cliOptions.add(enumPropertyNamingOpt.defaultValue(enumPropertyNaming.name())); + } + + @Override + public String apiDocFileFolder() { + return (outputFolder + "/" + apiDocPath).replace('/', File.separatorChar); + } + + @Override + public String apiFileFolder() { + return outputFolder + File.separator + sourceFolder + File.separator + apiPackage().replace('.', File.separatorChar); + } + + @Override + public String escapeQuotationMark(String input) { + // remove " to avoid code injection + return input.replace("\"", ""); + } + + @Override + public String escapeReservedWord(String name) { + // TODO: Allow enum escaping as an option (e.g. backticks vs append/prepend underscore vs match model property escaping). + return String.format("`%s`", name); + } + + @Override + public String escapeUnsafeCharacters(String input) { + return input.replace("*/", "*_/").replace("/*", "/_*"); + } + + public CodegenConstants.ENUM_PROPERTY_NAMING_TYPE getEnumPropertyNaming() { + return this.enumPropertyNaming; + } + + /** + * Sets the naming convention for Kotlin enum properties + * + * @param enumPropertyNamingType The string representation of the naming convention, as defined by {@link CodegenConstants.ENUM_PROPERTY_NAMING_TYPE} + */ + public void setEnumPropertyNaming(final String enumPropertyNamingType) { + try { + this.enumPropertyNaming = CodegenConstants.ENUM_PROPERTY_NAMING_TYPE.valueOf(enumPropertyNamingType); + } catch (IllegalArgumentException ex) { + StringBuilder sb = new StringBuilder(enumPropertyNamingType + " is an invalid enum property naming option. Please choose from:"); + for (CodegenConstants.ENUM_PROPERTY_NAMING_TYPE t : CodegenConstants.ENUM_PROPERTY_NAMING_TYPE.values()) { + sb.append("\n ").append(t.name()); + } + throw new RuntimeException(sb.toString()); + } + } + + /** + * returns the swagger type for the property + * + * @param p Swagger property object + * @return string presentation of the type + **/ + @Override + public String getSchemaType(Schema p) { + String openAPIType = super.getSchemaType(p); + String type; + // This maps, for example, long -> kotlin.Long based on hashes in this type's constructor + if (typeMapping.containsKey(openAPIType)) { + type = typeMapping.get(openAPIType); + if (languageSpecificPrimitives.contains(type)) { + return toModelName(type); + } + } else { + type = openAPIType; + } + return toModelName(type); + } + + /** + * Output the type declaration of the property + * + * @param p Swagger Property object + * @return a string presentation of the property type + */ + @Override + public String getTypeDeclaration(Schema p) { + if (p instanceof ArraySchema) { + return getArrayTypeDeclaration((ArraySchema) p); + } else if (p instanceof MapSchema) { + MapSchema mp = (MapSchema) p; + Schema inner = (Schema) mp.getAdditionalProperties(); + + // Maps will be keyed only by primitive Kotlin string + return getSchemaType(p) + ""; + } + return super.getTypeDeclaration(p); + } + + @Override + public String modelDocFileFolder() { + return (outputFolder + "/" + modelDocPath).replace('/', File.separatorChar); + } + + @Override + public String modelFileFolder() { + return outputFolder + File.separator + sourceFolder + File.separator + modelPackage().replace('.', File.separatorChar); + } + + @Override + public Map postProcessModels(Map objs) { + return postProcessModelsEnum(super.postProcessModels(objs)); + } + + @Override + public void processOpts() { + super.processOpts(); + + if (additionalProperties.containsKey(CodegenConstants.ENUM_PROPERTY_NAMING)) { + setEnumPropertyNaming((String) additionalProperties.get(CodegenConstants.ENUM_PROPERTY_NAMING)); + } + + if (additionalProperties.containsKey(CodegenConstants.SOURCE_FOLDER)) { + this.setSourceFolder((String) additionalProperties.get(CodegenConstants.SOURCE_FOLDER)); + } else { + additionalProperties.put(CodegenConstants.SOURCE_FOLDER, sourceFolder); + } + + if (additionalProperties.containsKey(CodegenConstants.PACKAGE_NAME)) { + this.setPackageName((String) additionalProperties.get(CodegenConstants.PACKAGE_NAME)); + if (!additionalProperties.containsKey(CodegenConstants.MODEL_PACKAGE)) + this.setModelPackage(packageName + ".models"); + if (!additionalProperties.containsKey(CodegenConstants.API_PACKAGE)) + this.setApiPackage(packageName + ".apis"); + } else { + additionalProperties.put(CodegenConstants.PACKAGE_NAME, packageName); + } + + if (additionalProperties.containsKey(CodegenConstants.ARTIFACT_ID)) { + this.setArtifactId((String) additionalProperties.get(CodegenConstants.ARTIFACT_ID)); + } else { + additionalProperties.put(CodegenConstants.ARTIFACT_ID, artifactId); + } + + if (additionalProperties.containsKey(CodegenConstants.GROUP_ID)) { + this.setGroupId((String) additionalProperties.get(CodegenConstants.GROUP_ID)); + } else { + additionalProperties.put(CodegenConstants.GROUP_ID, groupId); + } + + if (additionalProperties.containsKey(CodegenConstants.ARTIFACT_VERSION)) { + this.setArtifactVersion((String) additionalProperties.get(CodegenConstants.ARTIFACT_VERSION)); + } else { + additionalProperties.put(CodegenConstants.ARTIFACT_VERSION, artifactVersion); + } + + if (additionalProperties.containsKey(CodegenConstants.INVOKER_PACKAGE)) { + LOGGER.warn(CodegenConstants.INVOKER_PACKAGE + " with " + this.getName() + " generator is ignored. Use " + CodegenConstants.PACKAGE_NAME + "."); + } + + additionalProperties.put(CodegenConstants.API_PACKAGE, apiPackage()); + additionalProperties.put(CodegenConstants.MODEL_PACKAGE, modelPackage()); + + additionalProperties.put("apiDocPath", apiDocPath); + additionalProperties.put("modelDocPath", modelDocPath); + } + + public void setArtifactId(String artifactId) { + this.artifactId = artifactId; + } + + public void setArtifactVersion(String artifactVersion) { + this.artifactVersion = artifactVersion; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public void setPackageName(String packageName) { + this.packageName = packageName; + } + + public void setSourceFolder(String sourceFolder) { + this.sourceFolder = sourceFolder; + } + + /** + * Return the sanitized variable name for enum + * + * @param value enum variable name + * @param datatype data type + * @return the sanitized variable name for enum + */ + @Override + public String toEnumVarName(String value, String datatype) { + String modified; + if (value.length() == 0) { + modified = "EMPTY"; + } else { + modified = value; + modified = sanitizeKotlinSpecificNames(modified); + } + + switch (getEnumPropertyNaming()) { + case original: + // NOTE: This is provided as a last-case allowance, but will still result in reserved words being escaped. + modified = value; + break; + case camelCase: + // NOTE: Removes hyphens and underscores + modified = camelize(modified, true); + break; + case PascalCase: + // NOTE: Removes hyphens and underscores + String result = camelize(modified); + modified = titleCase(result); + break; + case snake_case: + // NOTE: Removes hyphens + modified = underscore(modified); + break; + case UPPERCASE: + modified = modified.toUpperCase(); + break; + } + + if (reservedWords.contains(modified)) { + return escapeReservedWord(modified); + } + + return modified; + } + + @Override + public String toInstantiationType(Schema p) { + if (p instanceof ArraySchema) { + return getArrayTypeDeclaration((ArraySchema) p); + } + return super.toInstantiationType(p); + } + + /** + * Return the fully-qualified "Model" name for import + * + * @param name the name of the "Model" + * @return the fully-qualified "Model" name for import + */ + @Override + public String toModelImport(String name) { + // toModelImport is called while processing operations, but DefaultCodegen doesn't + // define imports correctly with fully qualified primitives and models as defined in this generator. + if (needToImport(name)) { + return super.toModelImport(name); + } + + return name; + } + + /** + * Output the proper model name (capitalized). + * In case the name belongs to the TypeSystem it won't be renamed. + * + * @param name the name of the model + * @return capitalized model name + */ + @Override + public String toModelName(final String name) { + // Allow for explicitly configured kotlin.* and java.* types + if (name.startsWith("kotlin.") || name.startsWith("java.")) { + return name; + } + + // If importMapping contains name, assume this is a legitimate model name. + if (importMapping.containsKey(name)) { + return importMapping.get(name); + } + + String modifiedName = name.replaceAll("\\.", ""); + modifiedName = sanitizeKotlinSpecificNames(modifiedName); + + if (reservedWords.contains(modifiedName)) { + modifiedName = escapeReservedWord(modifiedName); + } + + return titleCase(modifiedName); + } + + /** + * Provides a strongly typed declaration for simple arrays of some type and arrays of arrays of some type. + * + * @param arr Array schema + * @return type declaration of array + */ + private String getArrayTypeDeclaration(ArraySchema arr) { + // TODO: collection type here should be fully qualified namespace to avoid model conflicts + // This supports arrays of arrays. + String arrayType = typeMapping.get("array"); + StringBuilder instantiationType = new StringBuilder(arrayType); + Schema items = arr.getItems(); + String nestedType = getTypeDeclaration(items); + // TODO: We may want to differentiate here between generics and primitive arrays. + instantiationType.append("<").append(nestedType).append(">"); + return instantiationType.toString(); + } + + /** + * Sanitize against Kotlin specific naming conventions, which may differ from those required by {@link DefaultCodegen#sanitizeName}. + * + * @param name string to be sanitize + * @return sanitized string + */ + private String sanitizeKotlinSpecificNames(final String name) { + String word = name; + for (Map.Entry specialCharacters : specialCharReplacements.entrySet()) { + // Underscore is the only special character we'll allow + if (!specialCharacters.getKey().equals("_")) { + word = word.replaceAll("\\Q" + specialCharacters.getKey() + "\\E", specialCharacters.getValue()); + } + } + + // Fallback, replace unknowns with underscore. + word = word.replaceAll("\\W+", "_"); + if (word.matches("\\d.*")) { + word = "_" + word; + } + + // _, __, and ___ are reserved in Kotlin. Treat all names with only underscores consistently, regardless of count. + if (word.matches("^_*$")) { + word = word.replaceAll("\\Q_\\E", "Underscore"); + } + + return word; + } + + private String titleCase(final String input) { + return input.substring(0, 1).toUpperCase() + input.substring(1); + } + + @Override + protected boolean isReservedWord(String word) { + // We want case-sensitive escaping, to avoid unnecessary backtick-escaping. + return reservedWords.contains(word); + } + + /** + * Check the type to see if it needs import the library/module/package + * + * @param type name of the type + * @return true if the library/module/package of the corresponding type needs to be imported + */ + @Override + protected boolean needToImport(String type) { + // provides extra protection against improperly trying to import language primitives and java types + boolean imports = !type.startsWith("kotlin.") && !type.startsWith("java.") && !defaultIncludes.contains(type) && !languageSpecificPrimitives.contains(type); + return imports; + } +} diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinClientCodegen.java new file mode 100644 index 00000000000..3c07882ab55 --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinClientCodegen.java @@ -0,0 +1,117 @@ +package org.openapitools.codegen.languages; + +import org.openapitools.codegen.CliOption; +import org.openapitools.codegen.CodegenConstants; +import org.openapitools.codegen.CodegenType; +import org.openapitools.codegen.SupportingFile; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +public class KotlinClientCodegen extends AbstractKotlinCodegen { + + public static final String DATE_LIBRARY = "dateLibrary"; + protected CodegenConstants.ENUM_PROPERTY_NAMING_TYPE enumPropertyNaming = CodegenConstants.ENUM_PROPERTY_NAMING_TYPE.camelCase; + static Logger LOGGER = LoggerFactory.getLogger(KotlinClientCodegen.class); + + protected String dateLibrary = DateLibrary.JAVA8.value; + + public enum DateLibrary { + STRING("string"), + THREETENBP("threetenbp"), + JAVA8("java8"); + + public final String value; + + DateLibrary(String value) { + this.value = value; + } + } + + /** + * Constructs an instance of `KotlinClientCodegen`. + */ + public KotlinClientCodegen() { + super(); + + artifactId = "kotlin-client"; + packageName = "io.swagger.client"; + + outputFolder = "generated-code" + File.separator + "kotlin-client"; + modelTemplateFiles.put("model.mustache", ".kt"); + apiTemplateFiles.put("api.mustache", ".kt"); + modelDocTemplateFiles.put("model_doc.mustache", ".md"); + apiDocTemplateFiles.put("api_doc.mustache", ".md"); + embeddedTemplateDir = templateDir = "kotlin-client"; + apiPackage = packageName + ".apis"; + modelPackage = packageName + ".models"; + + CliOption dateLibrary = new CliOption(DATE_LIBRARY, "Option. Date library to use"); + Map dateOptions = new HashMap<>(); + dateOptions.put(DateLibrary.THREETENBP.value, "Threetenbp"); + dateOptions.put(DateLibrary.STRING.value, "String"); + dateOptions.put(DateLibrary.JAVA8.value, "Java 8 native JSR310"); + dateLibrary.setEnum(dateOptions); + cliOptions.add(dateLibrary); + } + + public CodegenType getTag() { + return CodegenType.CLIENT; + } + + public String getName() { + return "kotlin"; + } + + public String getHelp() { + return "Generates a Kotlin client."; + } + + public void setDateLibrary(String library) { + this.dateLibrary = library; + } + + @Override + public void processOpts() { + super.processOpts(); + + if (additionalProperties.containsKey(DATE_LIBRARY)) { + setDateLibrary(additionalProperties.get(DATE_LIBRARY).toString()); + } + + if (DateLibrary.THREETENBP.value.equals(dateLibrary)) { + additionalProperties.put(DateLibrary.THREETENBP.value, true); + typeMapping.put("date", "LocalDate"); + typeMapping.put("DateTime", "LocalDateTime"); + importMapping.put("LocalDate", "org.threeten.bp.LocalDate"); + importMapping.put("LocalDateTime", "org.threeten.bp.LocalDateTime"); + defaultIncludes.add("org.threeten.bp.LocalDateTime"); + } else if (DateLibrary.STRING.value.equals(dateLibrary)) { + typeMapping.put("date-time", "kotlin.String"); + typeMapping.put("date", "kotlin.String"); + typeMapping.put("Date", "kotlin.String"); + typeMapping.put("DateTime", "kotlin.String"); + } else if (DateLibrary.JAVA8.value.equals(dateLibrary)) { + additionalProperties.put(DateLibrary.JAVA8.value, true); + } + + supportingFiles.add(new SupportingFile("README.mustache", "", "README.md")); + supportingFiles.add(new SupportingFile("build.gradle.mustache", "", "build.gradle")); + supportingFiles.add(new SupportingFile("settings.gradle.mustache", "", "settings.gradle")); + + final String infrastructureFolder = (sourceFolder + File.separator + packageName + File.separator + "infrastructure").replace(".", "/"); + + supportingFiles.add(new SupportingFile("infrastructure/ApiClient.kt.mustache", infrastructureFolder, "ApiClient.kt")); + supportingFiles.add(new SupportingFile("infrastructure/ApiAbstractions.kt.mustache", infrastructureFolder, "ApiAbstractions.kt")); + supportingFiles.add(new SupportingFile("infrastructure/ApiInfrastructureResponse.kt.mustache", infrastructureFolder, "ApiInfrastructureResponse.kt")); + supportingFiles.add(new SupportingFile("infrastructure/ApplicationDelegates.kt.mustache", infrastructureFolder, "ApplicationDelegates.kt")); + supportingFiles.add(new SupportingFile("infrastructure/RequestConfig.kt.mustache", infrastructureFolder, "RequestConfig.kt")); + supportingFiles.add(new SupportingFile("infrastructure/RequestMethod.kt.mustache", infrastructureFolder, "RequestMethod.kt")); + supportingFiles.add(new SupportingFile("infrastructure/ResponseExtensions.kt.mustache", infrastructureFolder, "ResponseExtensions.kt")); + supportingFiles.add(new SupportingFile("infrastructure/Serializer.kt.mustache", infrastructureFolder, "Serializer.kt")); + supportingFiles.add(new SupportingFile("infrastructure/Errors.kt.mustache", infrastructureFolder, "Errors.kt")); + } +} diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinServerCodegen.java new file mode 100644 index 00000000000..64721db805e --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinServerCodegen.java @@ -0,0 +1,230 @@ +package org.openapitools.codegen.languages; + +import com.google.common.collect.ImmutableMap; +import com.samskivert.mustache.Mustache; +import org.openapitools.codegen.CliOption; +import org.openapitools.codegen.CodegenConstants; +import org.openapitools.codegen.CodegenType; +import org.openapitools.codegen.SupportingFile; +import org.openapitools.codegen.mustache.*; + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.media.*; +import io.swagger.v3.oas.models.responses.ApiResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +public class KotlinServerCodegen extends AbstractKotlinCodegen { + + public static final String DEFAULT_LIBRARY = Constants.KTOR; + static Logger LOGGER = LoggerFactory.getLogger(KotlinServerCodegen.class); + private Boolean autoHeadFeatureEnabled = true; + private Boolean conditionalHeadersFeatureEnabled = false; + private Boolean hstsFeatureEnabled = true; + private Boolean corsFeatureEnabled = false; + private Boolean compressionFeatureEnabled = true; + + // This is here to potentially warn the user when an option is not supoprted by the target framework. + private Map> optionsSupportedPerFramework = new ImmutableMap.Builder>() + .put(Constants.KTOR, Arrays.asList( + Constants.AUTOMATIC_HEAD_REQUESTS, + Constants.CONDITIONAL_HEADERS, + Constants.HSTS, + Constants.CORS, + Constants.COMPRESSION + )) + .build(); + + /** + * Constructs an instance of `KotlinServerCodegen`. + */ + public KotlinServerCodegen() { + super(); + + artifactId = "kotlin-server"; + packageName = "io.swagger.server"; + outputFolder = "generated-code" + File.separator + "kotlin-server"; + modelTemplateFiles.put("model.mustache", ".kt"); + apiTemplateFiles.put("api.mustache", ".kt"); + embeddedTemplateDir = templateDir = "kotlin-server"; + apiPackage = packageName + ".apis"; + modelPackage = packageName + ".models"; + + supportedLibraries.put("ktor", "ktor framework"); + + // TODO: Configurable server engine. Defaults to netty in build.gradle. + CliOption library = new CliOption(CodegenConstants.LIBRARY, "library template (sub-template) to use"); + library.setDefault(DEFAULT_LIBRARY); + library.setEnum(supportedLibraries); + + cliOptions.add(library); + + addSwitch(Constants.AUTOMATIC_HEAD_REQUESTS, Constants.AUTOMATIC_HEAD_REQUESTS_DESC, getAutoHeadFeatureEnabled()); + addSwitch(Constants.CONDITIONAL_HEADERS, Constants.CONDITIONAL_HEADERS_DESC, getConditionalHeadersFeatureEnabled()); + addSwitch(Constants.HSTS, Constants.HSTS_DESC, getHstsFeatureEnabled()); + addSwitch(Constants.CORS, Constants.CORS_DESC, getCorsFeatureEnabled()); + addSwitch(Constants.COMPRESSION, Constants.COMPRESSION_DESC, getCompressionFeatureEnabled()); + } + + public Boolean getAutoHeadFeatureEnabled() { + return autoHeadFeatureEnabled; + } + + public void setAutoHeadFeatureEnabled(Boolean autoHeadFeatureEnabled) { + this.autoHeadFeatureEnabled = autoHeadFeatureEnabled; + } + + public Boolean getCompressionFeatureEnabled() { + return compressionFeatureEnabled; + } + + public void setCompressionFeatureEnabled(Boolean compressionFeatureEnabled) { + this.compressionFeatureEnabled = compressionFeatureEnabled; + } + + public Boolean getConditionalHeadersFeatureEnabled() { + return conditionalHeadersFeatureEnabled; + } + + public void setConditionalHeadersFeatureEnabled(Boolean conditionalHeadersFeatureEnabled) { + this.conditionalHeadersFeatureEnabled = conditionalHeadersFeatureEnabled; + } + + public Boolean getCorsFeatureEnabled() { + return corsFeatureEnabled; + } + + public void setCorsFeatureEnabled(Boolean corsFeatureEnabled) { + this.corsFeatureEnabled = corsFeatureEnabled; + } + + public String getHelp() { + return "Generates a Kotlin server."; + } + + public Boolean getHstsFeatureEnabled() { + return hstsFeatureEnabled; + } + + public void setHstsFeatureEnabled(Boolean hstsFeatureEnabled) { + this.hstsFeatureEnabled = hstsFeatureEnabled; + } + + public String getName() { + return "kotlin-server"; + } + + public CodegenType getTag() { + return CodegenType.SERVER; + } + + @Override + public void processOpts() { + super.processOpts(); + + if (additionalProperties.containsKey(CodegenConstants.LIBRARY)) { + this.setLibrary((String) additionalProperties.get(CodegenConstants.LIBRARY)); + } + + if (additionalProperties.containsKey(Constants.AUTOMATIC_HEAD_REQUESTS)) { + setAutoHeadFeatureEnabled(convertPropertyToBooleanAndWriteBack(Constants.AUTOMATIC_HEAD_REQUESTS)); + } else { + additionalProperties.put(Constants.AUTOMATIC_HEAD_REQUESTS, getAutoHeadFeatureEnabled()); + } + + if (additionalProperties.containsKey(Constants.CONDITIONAL_HEADERS)) { + setConditionalHeadersFeatureEnabled(convertPropertyToBooleanAndWriteBack(Constants.CONDITIONAL_HEADERS)); + } else { + additionalProperties.put(Constants.CONDITIONAL_HEADERS, getConditionalHeadersFeatureEnabled()); + } + + if (additionalProperties.containsKey(Constants.HSTS)) { + setHstsFeatureEnabled(convertPropertyToBooleanAndWriteBack(Constants.HSTS)); + } else { + additionalProperties.put(Constants.HSTS, getHstsFeatureEnabled()); + } + + if (additionalProperties.containsKey(Constants.CORS)) { + setCorsFeatureEnabled(convertPropertyToBooleanAndWriteBack(Constants.CORS)); + } else { + additionalProperties.put(Constants.CORS, getCorsFeatureEnabled()); + } + + if (additionalProperties.containsKey(Constants.COMPRESSION)) { + setCompressionFeatureEnabled(convertPropertyToBooleanAndWriteBack(Constants.COMPRESSION)); + } else { + additionalProperties.put(Constants.COMPRESSION, getCompressionFeatureEnabled()); + } + + Boolean generateApis = additionalProperties.containsKey(CodegenConstants.GENERATE_APIS) && (Boolean)additionalProperties.get(CodegenConstants.GENERATE_APIS); + String packageFolder = (sourceFolder + File.separator + packageName).replace(".", File.separator); + String resourcesFolder = "src/main/resources"; // not sure this can be user configurable. + + supportingFiles.add(new SupportingFile("README.mustache", "", "README.md")); + supportingFiles.add(new SupportingFile("Dockerfile.mustache", "", "Dockerfile")); + + supportingFiles.add(new SupportingFile("build.gradle.mustache", "", "build.gradle")); + supportingFiles.add(new SupportingFile("settings.gradle.mustache", "", "settings.gradle")); + supportingFiles.add(new SupportingFile("gradle.properties", "", "gradle.properties")); + + supportingFiles.add(new SupportingFile("AppMain.kt.mustache", packageFolder, "AppMain.kt")); + supportingFiles.add(new SupportingFile("Configuration.kt.mustache", packageFolder, "Configuration.kt")); + + if (generateApis) { + supportingFiles.add(new SupportingFile("Paths.kt.mustache", packageFolder, "Paths.kt")); + } + + supportingFiles.add(new SupportingFile("application.conf.mustache", resourcesFolder, "application.conf")); + supportingFiles.add(new SupportingFile("logback.xml", resourcesFolder, "logback.xml")); + + final String infrastructureFolder = (sourceFolder + File.separator + packageName + File.separator + "infrastructure").replace(".", File.separator); + + supportingFiles.add(new SupportingFile("ApiKeyAuth.kt.mustache", infrastructureFolder, "ApiKeyAuth.kt")); + + addMustacheLambdas(additionalProperties); + } + + private void addMustacheLambdas(Map objs) { + + Map lambdas = new ImmutableMap.Builder() + .put("lowercase", new LowercaseLambda().generator(this)) + .put("uppercase", new UppercaseLambda()) + .put("titlecase", new TitlecaseLambda()) + .put("camelcase", new CamelCaseLambda().generator(this)) + .put("indented", new IndentedLambda()) + .put("indented_8", new IndentedLambda(8, " ")) + .put("indented_12", new IndentedLambda(12, " ")) + .put("indented_16", new IndentedLambda(16, " ")) + .build(); + + if (objs.containsKey("lambda")) { + LOGGER.warn("An property named 'lambda' already exists. Mustache lambdas renamed from 'lambda' to '_lambda'. " + + "You'll likely need to use a custom template, " + + "see https://github.com/swagger-api/swagger-codegen#modifying-the-client-library-format. "); + objs.put("_lambda", lambdas); + } else { + objs.put("lambda", lambdas); + } + } + + public static class Constants { + public final static String KTOR = "ktor"; + public final static String AUTOMATIC_HEAD_REQUESTS = "featureAutoHead"; + public final static String AUTOMATIC_HEAD_REQUESTS_DESC = "Automatically provide responses to HEAD requests for existing routes that have the GET verb defined."; + public final static String CONDITIONAL_HEADERS = "featureConditionalHeaders"; + public final static String CONDITIONAL_HEADERS_DESC = "Avoid sending content if client already has same content, by checking ETag or LastModified properties."; + public final static String HSTS = "featureHSTS"; + public final static String HSTS_DESC = "Avoid sending content if client already has same content, by checking ETag or LastModified properties."; + public final static String CORS = "featureCORS"; + public final static String CORS_DESC = "Ktor by default provides an interceptor for implementing proper support for Cross-Origin Resource Sharing (CORS). See enable-cors.org."; + public final static String COMPRESSION = "featureCompression"; + public final static String COMPRESSION_DESC = "Adds ability to compress outgoing content using gzip, deflate or custom encoder and thus reduce size of the response."; + } +} diff --git a/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 5952912e7f6..28ab243b74f 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 @@ -7,6 +7,8 @@ org.openapitools.codegen.languages.ConfluenceWikiCodegen org.openapitools.codegen.languages.CppRestClientCodegen org.openapitools.codegen.languages.DartClientCodegen org.openapitools.codegen.languages.ElixirClientCodegen +org.openapitools.codegen.languages.KotlinClientCodegen +org.openapitools.codegen.languages.KotlinServerCodegen org.openapitools.codegen.languages.HaskellServantCodegen org.openapitools.codegen.languages.LumenServerCodegen org.openapitools.codegen.languages.ObjcClientCodegen From 40d238131724bb473980f01963ab2f45d7b155f2 Mon Sep 17 00:00:00 2001 From: wing328 Date: Wed, 28 Mar 2018 11:27:46 +0800 Subject: [PATCH 13/29] add kotlin server, set default lib to ktor --- .../codegen/languages/KotlinServerCodegen.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinServerCodegen.java index 64721db805e..a4364f1b5e2 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinServerCodegen.java @@ -2,6 +2,7 @@ package org.openapitools.codegen.languages; import com.google.common.collect.ImmutableMap; import com.samskivert.mustache.Mustache; +import org.apache.commons.lang3.StringUtils; import org.openapitools.codegen.CliOption; import org.openapitools.codegen.CodegenConstants; import org.openapitools.codegen.CodegenType; @@ -130,7 +131,14 @@ public class KotlinServerCodegen extends AbstractKotlinCodegen { super.processOpts(); if (additionalProperties.containsKey(CodegenConstants.LIBRARY)) { - this.setLibrary((String) additionalProperties.get(CodegenConstants.LIBRARY)); + this.setLibrary((String) additionalProperties.get(CodegenConstants.LIBRARY)); + } + + // set default library to "ktor" + if (StringUtils.isEmpty(library)) { + this.setLibrary("ktor"); + additionalProperties.put(CodegenConstants.LIBRARY, "ktor"); + LOGGER.info("`library` option is empty. Default to 'ktor'."); } if (additionalProperties.containsKey(Constants.AUTOMATIC_HEAD_REQUESTS)) { From 7ccdca36ada1e699c5d34b7691cac9a3503dd81c Mon Sep 17 00:00:00 2001 From: wing328 Date: Wed, 28 Mar 2018 11:41:55 +0800 Subject: [PATCH 14/29] add lua generator --- .../codegen/languages/LuaClientCodegen.java | 542 ++++++++++++++++++ .../org.openapitools.codegen.CodegenConfig | 1 + 2 files changed, 543 insertions(+) create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/LuaClientCodegen.java diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/LuaClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/LuaClientCodegen.java new file mode 100644 index 00000000000..17088e4f89a --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/LuaClientCodegen.java @@ -0,0 +1,542 @@ +package org.openapitools.codegen.languages; + +import org.openapitools.codegen.*; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.media.*; +import io.swagger.v3.oas.models.responses.ApiResponse; + +import java.io.File; +import java.util.*; + +import org.apache.commons.lang3.StringUtils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class LuaClientCodegen extends DefaultCodegen implements CodegenConfig { + static Logger LOGGER = LoggerFactory.getLogger(LuaClientCodegen.class); + + protected String specFolder = "spec"; + protected String packageName = "swagger-client"; + protected String packageVersion = "1.0.0-1"; + protected String apiDocPath = "docs/"; + protected String modelDocPath = "docs/"; + protected String luaRocksFilename = "swagger-client-1.0.0-1.rockspec"; + + public CodegenType getTag() { + return CodegenType.CLIENT; + } + + public String getName() { + return "lua"; + } + + public String getHelp() { + return "Generates a Lua client library (beta)."; + } + + public LuaClientCodegen() { + super(); + outputFolder = "generated-code/lua"; + modelTemplateFiles.put("model.mustache", ".lua"); + apiTemplateFiles.put("api.mustache", ".lua"); + + modelDocTemplateFiles.put("model_doc.mustache", ".md"); + apiDocTemplateFiles.put("api_doc.mustache", ".md"); + + embeddedTemplateDir = templateDir = "lua"; + + setReservedWordsLowerCase( + Arrays.asList( + // data type + "nil", "string", "boolean", "number", "userdata", "thread", + "table", + + // reserved words: http://www.lua.org/manual/5.1/manual.html#2.1 + "and", "break", "do", "else", "elseif", + "end", "false", "for", "function", "if", + "in", "local", "nil", "not", "or", + "repeat", "return", "then", "true", "until", "while" + ) + ); + + defaultIncludes = new HashSet( + Arrays.asList( + "map", + "array") + ); + + languageSpecificPrimitives = new HashSet( + Arrays.asList( + "nil", + "string", + "boolean", + "number") + ); + + instantiationTypes.clear(); + /*instantiationTypes.put("array", "LuaArray"); + instantiationTypes.put("map", "LuaMap");*/ + + typeMapping.clear(); + typeMapping.put("integer", "number"); + typeMapping.put("long", "number"); + typeMapping.put("number", "number"); + typeMapping.put("float", "number"); + typeMapping.put("double", "number"); + typeMapping.put("boolean", "boolean"); + typeMapping.put("string", "string"); + typeMapping.put("UUID", "string"); + typeMapping.put("date", "string"); + typeMapping.put("DateTime", "string"); + typeMapping.put("password", "string"); + // TODO fix file mapping + typeMapping.put("file", "string"); + // 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", "TODO_OBJECT_MAPPING"); + + importMapping = new HashMap(); + importMapping.put("time.Time", "time"); + importMapping.put("*os.File", "os"); + importMapping.put("os", "io/ioutil"); + + cliOptions.clear(); + cliOptions.add(new CliOption(CodegenConstants.PACKAGE_NAME, "Lua package name (convention: lowercase).") + .defaultValue("swagger-client")); + cliOptions.add(new CliOption(CodegenConstants.PACKAGE_VERSION, "Lua package version.") + .defaultValue("1.0.0-1")); + cliOptions.add(new CliOption(CodegenConstants.HIDE_GENERATION_TIMESTAMP, "hides the timestamp when files were generated") + .defaultValue(Boolean.TRUE.toString())); + + } + + @Override + public void processOpts() { + super.processOpts(); + + // default HIDE_GENERATION_TIMESTAMP to true + if (!additionalProperties.containsKey(CodegenConstants.HIDE_GENERATION_TIMESTAMP)) { + additionalProperties.put(CodegenConstants.HIDE_GENERATION_TIMESTAMP, Boolean.TRUE.toString()); + } else { + additionalProperties.put(CodegenConstants.HIDE_GENERATION_TIMESTAMP, + Boolean.valueOf(additionalProperties().get(CodegenConstants.HIDE_GENERATION_TIMESTAMP).toString())); + } + + if (additionalProperties.containsKey(CodegenConstants.PACKAGE_NAME)) { + setPackageName((String) additionalProperties.get(CodegenConstants.PACKAGE_NAME)); + } + + if (additionalProperties.containsKey(CodegenConstants.PACKAGE_VERSION)) { + setPackageVersion((String) additionalProperties.get(CodegenConstants.PACKAGE_VERSION)); + } + + additionalProperties.put(CodegenConstants.PACKAGE_NAME, packageName); + additionalProperties.put(CodegenConstants.PACKAGE_VERSION, packageVersion); + + // set rockspec based on package name, version + setLuaRocksFilename(packageName + "-" + packageVersion + ".rockspec"); + + additionalProperties.put("apiDocPath", apiDocPath); + additionalProperties.put("modelDocPath", modelDocPath); + + apiTestTemplateFiles.put("api_test.mustache", ".lua"); + modelTestTemplateFiles.put("model_test.mustache", ".lua"); + + apiDocTemplateFiles.clear(); // TODO: add api doc template + modelDocTemplateFiles.clear(); // TODO: add model doc template + + modelPackage = packageName; + apiPackage = packageName; + + //supportingFiles.add(new SupportingFile("README.mustache", "", "README.md")); + supportingFiles.add(new SupportingFile("git_push.sh.mustache", "", "git_push.sh")); + supportingFiles.add(new SupportingFile("gitignore.mustache", "", ".gitignore")); + supportingFiles.add(new SupportingFile("luarocks.mustache", "", luaRocksFilename)); + //supportingFiles.add(new SupportingFile("configuration.mustache", "", "configuration.lua")); + //supportingFiles.add(new SupportingFile("api_client.mustache", "", "api_client.lua")); + //supportingFiles.add(new SupportingFile("api_response.mustache", "", "api_response.lua")); + //supportingFiles.add(new SupportingFile(".travis.yml", "", ".travis.yml")); + } + + @Override + public String escapeReservedWord(String name) { + // Can't start with an underscore, as our fields need to start with an + // UppercaseLetter so that Lua 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() { + return outputFolder + File.separator + packageName + File.separator + "api" + File.separator; + } + + public String modelFileFolder() { + return outputFolder + File.separator + packageName + File.separator + "model" + File.separator; + } + + @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; + + // convert variable name to snake case + // PetId => pet_id + name = underscore(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) { + return toVarName(name); + } + + @Override + public String toModelName(String name) { + return toModelFilename(name); + } + + @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.lua => pet_api.lua + return underscore(name) + "_api"; + } + + @Override + public String toApiTestFilename(String name) { + return toApiFilename(name) + "_spec"; + } + + @Override + public String toModelTestFilename(String name) { + return toModelFilename(name) + "_spec"; + } + + /** + * 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) { + + } + + @Override + public String apiTestFileFolder() { + return outputFolder + File.separator + specFolder.replace("/", File.separator); + } + + @Override + public String modelTestFileFolder() { + return outputFolder + File.separator + specFolder.replace("/", File.separator); + } + + @Override + public String apiDocFileFolder() { + return (outputFolder + "/" + apiDocPath).replace('/', File.separatorChar); + } + + @Override + public String modelDocFileFolder() { + 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 toApiName(String name) { + return underscore(super.toApiName(name)); + } + + @Override + public String getTypeDeclaration(Schema p) { + if (p instanceof ArraySchema) { + ArraySchema ap = (ArraySchema) p; + Schema inner = ap.getItems(); + return getTypeDeclaration(inner); + } else if (p instanceof MapSchema) { + MapSchema mp = (MapSchema) p; + Schema inner = (Schema) mp.getAdditionalProperties(); + return getTypeDeclaration(inner); + } + + // Not using the supertype invocation, because we want to UpperCamelize + // the type. + String schemaType = getSchemaType(p); + 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 p) { + String schemaType = super.getSchemaType(p); + String type = null; + 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 " + underscore("call_" + operationId)); + sanitizedOperationId = "call_" + sanitizedOperationId; + } + + return underscore(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 op : operations) { + + String[] items = op.path.split("/", -1); + String luaPath = ""; + int pathParamIndex = 0; + + for (int i = 0; i < items.length; ++i) { + if (items[i].matches("^\\{(.*)\\}$")) { // wrap in {} + // find the datatype of the parameter + //final CodegenParameter cp = op.pathParams.get(pathParamIndex); + // TODO: Handle non-primitives… + //luaPath = luaPath + cp.dataType.toLowerCase(); + luaPath = luaPath + "/%s"; + pathParamIndex++; + } else if (items[i].length() != 0) { + luaPath = luaPath + "/" + items[i]; + } else { + //luaPath = luaPath + "/"; + } + } + op.vendorExtensions.put("x-codegen-path", luaPath); + } + 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 postProcessModelsEnum(objs); + } + + @Override + protected boolean needToImport(String type) { + return !defaultIncludes.contains(type) + && !languageSpecificPrimitives.contains(type); + } + + public void setPackageName(String packageName) { + this.packageName = packageName; + } + + public void setPackageVersion(String packageVersion) { + this.packageVersion = packageVersion; + } + + public void setLuaRocksFilename(String luaRocksFilename) { + this.luaRocksFilename = luaRocksFilename; + } + + @Override + public String escapeQuotationMark(String input) { + // remove " to avoid code injection + return input.replace("\"", ""); + } + + @Override + public String escapeUnsafeCharacters(String input) { + return input.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; + } + } + + @Override + public String toModelImport(String name) { + if (needToImport(toModelName(name))) { + return toModelName(name); + } + + return name; + } +} 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 28ab243b74f..6c35a094d35 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 @@ -10,6 +10,7 @@ org.openapitools.codegen.languages.ElixirClientCodegen org.openapitools.codegen.languages.KotlinClientCodegen org.openapitools.codegen.languages.KotlinServerCodegen org.openapitools.codegen.languages.HaskellServantCodegen +org.openapitools.codegen.languages.LuaClientCodegen org.openapitools.codegen.languages.LumenServerCodegen org.openapitools.codegen.languages.ObjcClientCodegen org.openapitools.codegen.languages.PhpClientCodegen From 0b89519cf8f50fb8738a133b8db5c66ea13735fb Mon Sep 17 00:00:00 2001 From: wing328 Date: Wed, 28 Mar 2018 11:48:42 +0800 Subject: [PATCH 15/29] add python generator --- .../languages/PythonClientCodegen.java | 60 +++++++++---------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonClientCodegen.java index b37aae3c608..2a68c2c5701 100755 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonClientCodegen.java @@ -9,10 +9,11 @@ import org.openapitools.codegen.CodegenProperty; import org.openapitools.codegen.CodegenType; import org.openapitools.codegen.DefaultCodegen; 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 io.swagger.v3.oas.models.responses.ApiResponse; +import io.swagger.v3.parser.util.SchemaTypeUtil; import java.io.File; import java.util.ArrayList; @@ -101,16 +102,16 @@ public class PythonClientCodegen extends DefaultCodegen implements CodegenConfig // from https://docs.python.org/3/reference/lexical_analysis.html#keywords setReservedWordsLowerCase( Arrays.asList( - // local variable name used in API methods (endpoints) - "all_params", "resource_path", "path_params", "query_params", - "header_params", "form_params", "local_var_files", "body_params", "auth_settings", - // @property - "property", - // python reserved words - "and", "del", "from", "not", "while", "as", "elif", "global", "or", "with", - "assert", "else", "if", "pass", "yield", "break", "except", "import", - "print", "class", "exec", "in", "raise", "continue", "finally", "is", - "return", "def", "for", "lambda", "try", "self", "nonlocal", "None", "True", "False")); + // local variable name used in API methods (endpoints) + "all_params", "resource_path", "path_params", "query_params", + "header_params", "form_params", "local_var_files", "body_params", "auth_settings", + // @property + "property", + // python reserved words + "and", "del", "from", "not", "while", "as", "elif", "global", "or", "with", + "assert", "else", "if", "pass", "yield", "break", "except", "import", + "print", "class", "exec", "in", "raise", "continue", "finally", "is", + "return", "def", "for", "lambda", "try", "self", "nonlocal", "None", "True", "False")); regexModifiers = new HashMap(); regexModifiers.put('i', "IGNORECASE"); @@ -146,21 +147,19 @@ public class PythonClientCodegen extends DefaultCodegen implements CodegenConfig super.processOpts(); Boolean excludeTests = false; - if(additionalProperties.containsKey(CodegenConstants.EXCLUDE_TESTS)) { + if (additionalProperties.containsKey(CodegenConstants.EXCLUDE_TESTS)) { excludeTests = Boolean.valueOf(additionalProperties.get(CodegenConstants.EXCLUDE_TESTS).toString()); } if (additionalProperties.containsKey(CodegenConstants.PACKAGE_NAME)) { setPackageName((String) additionalProperties.get(CodegenConstants.PACKAGE_NAME)); - } - else { + } else { setPackageName("swagger_client"); } if (additionalProperties.containsKey(CodegenConstants.PROJECT_NAME)) { setProjectName((String) additionalProperties.get(CodegenConstants.PROJECT_NAME)); - } - else { + } else { // default: set project based on package name // e.g. petstore_api (package name) => petstore-api (project name) setProjectName(packageName.replaceAll("_", "-")); @@ -168,8 +167,7 @@ public class PythonClientCodegen extends DefaultCodegen implements CodegenConfig if (additionalProperties.containsKey(CodegenConstants.PACKAGE_VERSION)) { setPackageVersion((String) additionalProperties.get(CodegenConstants.PACKAGE_VERSION)); - } - else { + } else { setPackageVersion("1.0.0"); } @@ -204,7 +202,7 @@ public class PythonClientCodegen extends DefaultCodegen implements CodegenConfig supportingFiles.add(new SupportingFile("__init__model.mustache", packageName + File.separatorChar + modelPackage, "__init__.py")); supportingFiles.add(new SupportingFile("__init__api.mustache", packageName + File.separatorChar + apiPackage, "__init__.py")); - if(Boolean.FALSE.equals(excludeTests)) { + if (Boolean.FALSE.equals(excludeTests)) { supportingFiles.add(new SupportingFile("__init__test.mustache", testFolder, "__init__.py")); } supportingFiles.add(new SupportingFile("git_push.sh.mustache", "", "git_push.sh")); @@ -235,14 +233,14 @@ public class PythonClientCodegen extends DefaultCodegen implements CodegenConfig @Override public String toModelImport(String name) { String modelImport; - if (StringUtils.startsWithAny(name,"import", "from")) { + if (StringUtils.startsWithAny(name, "import", "from")) { modelImport = name; } else { modelImport = "from "; if (!"".equals(modelPackage())) { modelImport += modelPackage() + "."; } - modelImport += toModelFilename(name)+ " import " + name; + modelImport += toModelFilename(name) + " import " + name; } return modelImport; } @@ -254,7 +252,7 @@ public class PythonClientCodegen extends DefaultCodegen implements CodegenConfig } @Override - public void postProcessParameter(CodegenParameter parameter){ + public void postProcessParameter(CodegenParameter parameter) { postProcessPattern(parameter.pattern, parameter.vendorExtensions); } @@ -268,21 +266,21 @@ public class PythonClientCodegen extends DefaultCodegen implements CodegenConfig * does not support this in as natural a way so it needs to convert it. See * https://docs.python.org/2/howto/regex.html#compilation-flags for details. */ - public void postProcessPattern(String pattern, Map vendorExtensions){ - if(pattern != null) { + public void postProcessPattern(String pattern, Map vendorExtensions) { + if (pattern != null) { int i = pattern.lastIndexOf('/'); //Must follow Perl /pattern/modifiers convention - if(pattern.charAt(0) != '/' || i < 2) { + if (pattern.charAt(0) != '/' || i < 2) { throw new IllegalArgumentException("Pattern must follow the Perl " - + "/pattern/modifiers convention. "+pattern+" is not valid."); + + "/pattern/modifiers convention. " + pattern + " is not valid."); } String regex = pattern.substring(1, i).replace("'", "\\'"); List modifiers = new ArrayList(); - for(char c : pattern.substring(i).toCharArray()) { - if(regexModifiers.containsKey(c)) { + for (char c : pattern.substring(i).toCharArray()) { + if (regexModifiers.containsKey(c)) { String modifier = regexModifiers.get(c); modifiers.add(modifier); } @@ -310,7 +308,7 @@ public class PythonClientCodegen extends DefaultCodegen implements CodegenConfig @Override public String escapeReservedWord(String name) { - if(this.reservedWordsMappings().containsKey(name)) { + if (this.reservedWordsMappings().containsKey(name)) { return this.reservedWordsMappings().get(name); } return "_" + name; @@ -521,7 +519,7 @@ public class PythonClientCodegen extends DefaultCodegen implements CodegenConfig } public void setProjectName(String projectName) { - this.projectName= projectName; + this.projectName = projectName; } public void setPackageVersion(String packageVersion) { @@ -534,7 +532,7 @@ public class PythonClientCodegen extends DefaultCodegen implements CodegenConfig /** * Generate Python package name from String `packageName` - * + *

* (PEP 0008) Python packages should also have short, all-lowercase names, * although the use of underscores is discouraged. * From 077c1de300f6158ca72b4df53773ed617e5c0f75 Mon Sep 17 00:00:00 2001 From: wing328 Date: Wed, 28 Mar 2018 15:33:21 +0800 Subject: [PATCH 16/29] add python flask generator --- .../PythonFlaskConnexionServerCodegen.java | 695 ++++++++++++++++++ ...gen.java => RubyOnRailsServerCodegen.java} | 6 +- .../org.openapitools.codegen.CodegenConfig | 3 +- 3 files changed, 700 insertions(+), 4 deletions(-) create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonFlaskConnexionServerCodegen.java rename modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/{Rails5ServerCodegen.java => RubyOnRailsServerCodegen.java} (98%) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonFlaskConnexionServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonFlaskConnexionServerCodegen.java new file mode 100644 index 00000000000..45381028079 --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonFlaskConnexionServerCodegen.java @@ -0,0 +1,695 @@ +package org.openapitools.codegen.languages; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Multimap; + +import org.openapitools.codegen.*; +import io.swagger.v3.oas.models.media.*; +import io.swagger.v3.oas.models.PathItem; +import io.swagger.v3.oas.models.PathItem.HttpMethod; +import io.swagger.v3.oas.models.*; +import io.swagger.v3.oas.models.parameters.*; +import io.swagger.v3.core.util.Yaml; + +import java.io.File; +import java.util.*; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class PythonFlaskConnexionServerCodegen extends DefaultCodegen implements CodegenConfig { + private static final Logger LOGGER = LoggerFactory.getLogger(PythonFlaskConnexionServerCodegen.class); + + public static final String CONTROLLER_PACKAGE = "controllerPackage"; + public static final String DEFAULT_CONTROLLER = "defaultController"; + public static final String SUPPORT_PYTHON2 = "supportPython2"; + + protected int serverPort = 8080; + protected String packageName; + protected String packageVersion; + protected String controllerPackage; + protected String defaultController; + protected Map regexModifiers; + + public PythonFlaskConnexionServerCodegen() { + super(); + modelPackage = "models"; + testPackage = "test"; + + languageSpecificPrimitives.clear(); + languageSpecificPrimitives.add("int"); + languageSpecificPrimitives.add("float"); + languageSpecificPrimitives.add("List"); + languageSpecificPrimitives.add("Dict"); + languageSpecificPrimitives.add("bool"); + languageSpecificPrimitives.add("str"); + languageSpecificPrimitives.add("datetime"); + languageSpecificPrimitives.add("date"); + languageSpecificPrimitives.add("file"); + languageSpecificPrimitives.add("object"); + + typeMapping.clear(); + typeMapping.put("integer", "int"); + typeMapping.put("float", "float"); + typeMapping.put("number", "float"); + typeMapping.put("long", "int"); + typeMapping.put("double", "float"); + typeMapping.put("array", "List"); + typeMapping.put("map", "Dict"); + typeMapping.put("boolean", "bool"); + typeMapping.put("string", "str"); + typeMapping.put("date", "date"); + typeMapping.put("DateTime", "datetime"); + typeMapping.put("object", "object"); + typeMapping.put("file", "file"); + typeMapping.put("UUID", "str"); + + // from https://docs.python.org/3/reference/lexical_analysis.html#keywords + setReservedWordsLowerCase( + Arrays.asList( + // @property + "property", + // python reserved words + "and", "del", "from", "not", "while", "as", "elif", "global", "or", "with", + "assert", "else", "if", "pass", "yield", "break", "except", "import", + "print", "class", "exec", "in", "raise", "continue", "finally", "is", + "return", "def", "for", "lambda", "try", "self", "None", "True", "False", "nonlocal")); + + // set the output folder here + outputFolder = "generated-code/connexion"; + + apiTemplateFiles.put("controller.mustache", ".py"); + modelTemplateFiles.put("model.mustache", ".py"); + apiTestTemplateFiles().put("controller_test.mustache", ".py"); + + /* + * Template Location. This is the location which templates will be read from. The generator + * will use the resource stream to attempt to read the templates. + */ + embeddedTemplateDir = templateDir = "flaskConnexion"; + + /* + * Additional Properties. These values can be passed to the templates and + * are available in models, apis, and supporting files + */ + additionalProperties.put("serverPort", serverPort); + + /* + * 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("README.mustache", "", "README.md")); + supportingFiles.add(new SupportingFile("setup.mustache", "", "setup.py")); + supportingFiles.add(new SupportingFile("tox.mustache", "", "tox.ini")); + supportingFiles.add(new SupportingFile("test-requirements.mustache", "", "test-requirements.txt")); + supportingFiles.add(new SupportingFile("requirements.mustache", "", "requirements.txt")); + supportingFiles.add(new SupportingFile("git_push.sh.mustache", "", "git_push.sh")); + supportingFiles.add(new SupportingFile("gitignore.mustache", "", ".gitignore")); + supportingFiles.add(new SupportingFile("travis.mustache", "", ".travis.yml")); + supportingFiles.add(new SupportingFile("Dockerfile.mustache", "", "Dockerfile")); + supportingFiles.add(new SupportingFile("dockerignore.mustache", "", ".dockerignore")); + + regexModifiers = new HashMap(); + regexModifiers.put('i', "IGNORECASE"); + regexModifiers.put('l', "LOCALE"); + regexModifiers.put('m', "MULTILINE"); + regexModifiers.put('s', "DOTALL"); + regexModifiers.put('u', "UNICODE"); + regexModifiers.put('x', "VERBOSE"); + + cliOptions.add(new CliOption(CodegenConstants.PACKAGE_NAME, "python package name (convention: snake_case).") + .defaultValue("swagger_server")); + cliOptions.add(new CliOption(CodegenConstants.PACKAGE_VERSION, "python package version.") + .defaultValue("1.0.0")); + cliOptions.add(new CliOption(CONTROLLER_PACKAGE, "controller package"). + defaultValue("controllers")); + cliOptions.add(new CliOption(DEFAULT_CONTROLLER, "default controller"). + defaultValue("default_controller")); + cliOptions.add(new CliOption(SUPPORT_PYTHON2, "support python2"). + defaultValue("false")); + cliOptions.add(new CliOption("serverPort", "TCP port to listen to in app.run"). + defaultValue("8080")); + } + + @Override + public void processOpts() { + super.processOpts(); + //apiTemplateFiles.clear(); + + if (additionalProperties.containsKey(CodegenConstants.PACKAGE_NAME)) { + setPackageName((String) additionalProperties.get(CodegenConstants.PACKAGE_NAME)); + } else { + setPackageName("swagger_server"); + additionalProperties.put(CodegenConstants.PACKAGE_NAME, this.packageName); + } + if (additionalProperties.containsKey(CodegenConstants.PACKAGE_VERSION)) { + setPackageVersion((String) additionalProperties.get(CodegenConstants.PACKAGE_VERSION)); + } else { + setPackageVersion("1.0.0"); + additionalProperties.put(CodegenConstants.PACKAGE_VERSION, this.packageVersion); + } + if (additionalProperties.containsKey(CONTROLLER_PACKAGE)) { + this.controllerPackage = additionalProperties.get(CONTROLLER_PACKAGE).toString(); + } else { + this.controllerPackage = "controllers"; + additionalProperties.put(CONTROLLER_PACKAGE, this.controllerPackage); + } + if (additionalProperties.containsKey(DEFAULT_CONTROLLER)) { + this.defaultController = additionalProperties.get(DEFAULT_CONTROLLER).toString(); + } else { + this.defaultController = "default_controller"; + additionalProperties.put(DEFAULT_CONTROLLER, this.defaultController); + } + if (Boolean.TRUE.equals(additionalProperties.get(SUPPORT_PYTHON2))) { + additionalProperties.put(SUPPORT_PYTHON2, Boolean.TRUE); + typeMapping.put("long", "long"); + } + supportingFiles.add(new SupportingFile("__init__.mustache", packageName, "__init__.py")); + supportingFiles.add(new SupportingFile("__main__.mustache", packageName, "__main__.py")); + supportingFiles.add(new SupportingFile("encoder.mustache", packageName, "encoder.py")); + supportingFiles.add(new SupportingFile("util.mustache", packageName, "util.py")); + supportingFiles.add(new SupportingFile("__init__.mustache", packageName + File.separatorChar + controllerPackage, "__init__.py")); + supportingFiles.add(new SupportingFile("__init__model.mustache", packageName + File.separatorChar + modelPackage, "__init__.py")); + supportingFiles.add(new SupportingFile("base_model_.mustache", packageName + File.separatorChar + modelPackage, "base_model_.py")); + supportingFiles.add(new SupportingFile("__init__test.mustache", packageName + File.separatorChar + testPackage, "__init__.py")); + supportingFiles.add(new SupportingFile("swagger.mustache", packageName + File.separatorChar + "swagger", "swagger.yaml")); + + modelPackage = packageName + "." + modelPackage; + controllerPackage = packageName + "." + controllerPackage; + testPackage = packageName + "." + testPackage; + } + + private static String dropDots(String str) { + return str.replaceAll("\\.", "_"); + } + + @Override + public String apiPackage() { + return controllerPackage; + } + + /** + * Configures the type of generator. + * + * @return the CodegenType for this generator + * @see org.openapitools.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 "python-flask"; + } + + /** + * 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 Python server library using the Connexion project. By default, " + + "it will also generate service classes -- which you can disable with the `-Dnoservice` environment variable."; + } + + @Override + public String toApiName(String name) { + if (name == null || name.length() == 0) { + return "DefaultController"; + } + return camelize(name, false) + "Controller"; + } + + @Override + public String toApiFilename(String name) { + return underscore(toApiName(name)); + } + + @Override + public String toApiTestFilename(String name) { + return "test_" + toApiFilename(name); + } + + /** + * 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) { + if (this.reservedWordsMappings().containsKey(name)) { + return this.reservedWordsMappings().get(name); + } + return "_" + name; // add an underscore to the name + } + + /** + * 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 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) + "[str, " + 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 type; + } + } else { + type = toModelName(swaggerType); + } + return type; + } + + @Override + public void preprocessOpenAPI(OpenAPI openAPI) { + // need vendor extensions for x-swagger-router-controller + Map paths = openAPI.getPaths(); + if (paths != null) { + for (String pathname : paths.keySet()) { + PathItem path = paths.get(pathname); + Map operationMap = path.readOperationsMap(); + if (operationMap != null) { + for (HttpMethod method : operationMap.keySet()) { + Operation operation = operationMap.get(method); + String tag = "default"; + if (operation.getTags() != null && operation.getTags().size() > 0) { + tag = operation.getTags().get(0); + } + String operationId = operation.getOperationId(); + if (operationId == null) { + operationId = getOrGenerateOperationId(operation, pathname, method.toString()); + } + operation.setOperationId(toOperationId(operationId)); + if (operation.getExtensions().get("x-swagger-router-controller") == null) { + operation.getExtensions().put( + "x-swagger-router-controller", + controllerPackage + "." + toApiFilename(tag) + ); + } + } + } + } + } + } + + @SuppressWarnings("unchecked") + private static List> getOperations(Map objs) { + List> result = new ArrayList>(); + Map apiInfo = (Map) objs.get("apiInfo"); + List> apis = (List>) apiInfo.get("apis"); + for (Map api : apis) { + result.add((Map) api.get("operations")); + } + return result; + } + + private static List> sortOperationsByPath(List ops) { + Multimap opsByPath = ArrayListMultimap.create(); + + for (CodegenOperation op : ops) { + opsByPath.put(op.path, op); + } + + List> opsByPathList = new ArrayList>(); + for (Map.Entry> entry : opsByPath.asMap().entrySet()) { + Map opsByPathEntry = new HashMap(); + opsByPathList.add(opsByPathEntry); + opsByPathEntry.put("path", entry.getKey()); + opsByPathEntry.put("operation", entry.getValue()); + List operationsForThisPath = Lists.newArrayList(entry.getValue()); + operationsForThisPath.get(operationsForThisPath.size() - 1).hasMore = false; + if (opsByPathList.size() < opsByPath.asMap().size()) { + opsByPathEntry.put("hasMore", "true"); + } + } + + return opsByPathList; + } + + @Override + public Map postProcessSupportingFileData(Map objs) { + OpenAPI swagger = (OpenAPI) objs.get("openapi"); + if (swagger != null) { + try { + objs.put("swagger-yaml", Yaml.mapper().writeValueAsString(swagger)); + } catch (JsonProcessingException e) { + LOGGER.error(e.getMessage(), e); + } + } + for (Map operations : getOperations(objs)) { + @SuppressWarnings("unchecked") + List ops = (List) operations.get("operation"); + + List> opsByPathList = sortOperationsByPath(ops); + operations.put("operationsByPath", opsByPathList); + } + return super.postProcessSupportingFileData(objs); + } + + @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'. + + // remove dollar sign + name = name.replaceAll("$", ""); + + // if it's all uppper case, convert to lower case + if (name.matches("^[A-Z_]*$")) { + name = name.toLowerCase(); + } + + // underscore the variable name + // petId => pet_id + name = underscore(name); + + // remove leading underscore + name = name.replaceAll("^_*", ""); + + // 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) { + // don't do name =removeNonNameElementToCamelCase(name); // this breaks connexion, which does not modify param names before sending them + if (reservedWords.contains(name)) { + return escapeReservedWord(name); + } + + // sanitize the param name but don't underscore it since it's used for request mapping + String paramName = sanitizeName(name); + if (!paramName.equals(name)) { + LOGGER.warn(name + " (parameter name) cannot be used as parameter name with flask-connexion and was sanitized as " + paramName); + } + // Param name is already sanitized in swagger spec processing + return paramName; + } + + @Override + public String toModelFilename(String name) { + // underscore the model file name + // PhoneNumber => phone_number + return underscore(dropDots(toModelName(name))); + } + + @Override + public String toModelName(String name) { + name = sanitizeName(name); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. + // remove dollar sign + name = name.replaceAll("$", ""); + + // 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 " + camelize("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 " + camelize("model_" + name)); + name = "model_" + name; // e.g. 200Response => Model200Response (after camelize) + } + + if (!StringUtils.isEmpty(modelNamePrefix)) { + name = modelNamePrefix + "_" + name; + } + + if (!StringUtils.isEmpty(modelNameSuffix)) { + name = name + "_" + modelNameSuffix; + } + + // camelize the model name + // phone_number => PhoneNumber + return camelize(name); + } + + @Override + public String toOperationId(String operationId) { + // throw exception if method name is empty (should not occur as an auto-generated method name will be used) + 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)) { + LOGGER.warn(operationId + " (reserved word) cannot be used as method name. Renamed to " + underscore(sanitizeName("call_" + operationId))); + operationId = "call_" + operationId; + } + + return underscore(sanitizeName(operationId)); + } + + /** + * Return the default value of the property + * + * @param p Swagger property object + * @return string presentation of the default value of the property + */ + @Override + public String toDefaultValue(Schema p) { + if (p instanceof StringSchema) { + StringSchema dp = (StringSchema) p; + if (dp.getDefault() != null) { + return "'" + dp.getDefault() + "'"; + } + } else if (p instanceof BooleanSchema) { + BooleanSchema dp = (BooleanSchema) p; + if (dp.getDefault() != null) { + if (dp.getDefault().toString().equalsIgnoreCase("false")) + return "False"; + else + return "True"; + } + } else if (p instanceof DateSchema) { + // TODO + } else if (p instanceof DateTimeSchema) { + // TODO + } else if (p instanceof NumberSchema) { + NumberSchema dp = (NumberSchema) p; + if (dp.getDefault() != null) { + return dp.getDefault().toString(); + } + } else if (p instanceof IntegerSchema) { + IntegerSchema dp = (IntegerSchema) p; + if (dp.getDefault() != null) { + return dp.getDefault().toString(); + } + } + + return null; + } + + @Override + public void setParameterExampleValue(CodegenParameter p) { + String example; + + if (p.defaultValue == null) { + example = p.example; + } else { + example = p.defaultValue; + } + + String type = p.baseType; + if (type == null) { + type = p.dataType; + } + + if ("String".equalsIgnoreCase(type) || "str".equalsIgnoreCase(type)) { + if (example == null) { + example = p.paramName + "_example"; + } + example = "'" + escapeText(example) + "'"; + } else if ("Integer".equals(type) || "int".equals(type)) { + if (p.minimum != null) { + example = "" + (Integer.valueOf(p.minimum) + 1); + } + if (p.maximum != null) { + example = "" + p.maximum; + } else if (example == null) { + example = "56"; + } + + } else if ("Long".equalsIgnoreCase(type)) { + if (p.minimum != null) { + example = "" + (Long.valueOf(p.minimum) + 1); + } + if (p.maximum != null) { + example = "" + p.maximum; + } else if (example == null) { + example = "789"; + } + } else if ("Float".equalsIgnoreCase(type) || "Double".equalsIgnoreCase(type)) { + if (p.minimum != null) { + example = "" + p.minimum; + } else if (p.maximum != null) { + example = "" + p.maximum; + } else if (example == null) { + example = "3.4"; + } + } else if ("BOOLEAN".equalsIgnoreCase(type) || "bool".equalsIgnoreCase(type)) { + if (example == null) { + example = "True"; + } + } else if ("file".equalsIgnoreCase(type)) { + example = "(BytesIO(b'some file data'), 'file.txt')"; + } else if ("Date".equalsIgnoreCase(type)) { + if (example == null) { + example = "2013-10-20"; + } + example = "'" + escapeText(example) + "'"; + } else if ("DateTime".equalsIgnoreCase(type)) { + if (example == null) { + example = "2013-10-20T19:20:30+01:00"; + } + example = "'" + escapeText(example) + "'"; + } else if (!languageSpecificPrimitives.contains(type)) { + // type is a model class, e.g. User + example = type + "()"; + } else { + LOGGER.warn("Type " + type + " not handled properly in setParameterExampleValue"); + } + + if (p.items != null && p.items.defaultValue != null) { + example = p.items.defaultValue; + } + if (example == null) { + example = "None"; + } else if (Boolean.TRUE.equals(p.isListContainer)) { + if (Boolean.TRUE.equals(p.isBodyParam)) { + example = "[" + example + "]"; + } + } else if (Boolean.TRUE.equals(p.isMapContainer)) { + example = "{'key': " + example + "}"; + } + + p.example = example; + } + + public void setPackageName(String packageName) { + this.packageName = packageName; + } + + public void setPackageVersion(String packageVersion) { + this.packageVersion = packageVersion; + } + + + @Override + public String escapeQuotationMark(String input) { + // remove ' to avoid code injection + return input.replace("'", ""); + } + + @Override + public String escapeUnsafeCharacters(String input) { + // remove multiline comment + return input.replace("'''", "'_'_'"); + } + + @Override + public String toModelImport(String name) { + String modelImport; + if (StringUtils.startsWithAny(name, "import", "from")) { + modelImport = name; + } else { + modelImport = "from "; + if (!"".equals(modelPackage())) { + modelImport += modelPackage() + "."; + } + modelImport += toModelFilename(name) + " import " + name; + } + return modelImport; + } + + @Override + public void postProcessModelProperty(CodegenModel model, CodegenProperty property) { + if (StringUtils.isNotEmpty(property.pattern)) { + addImport(model, "import re"); + } + postProcessPattern(property.pattern, property.vendorExtensions); + } + + @Override + public Map postProcessModels(Map objs) { + // process enum in models + return postProcessModelsEnum(objs); + } + + @Override + public void postProcessParameter(CodegenParameter parameter) { + postProcessPattern(parameter.pattern, parameter.vendorExtensions); + } + + /* + * The swagger pattern spec follows the Perl convention and style of modifiers. Python + * does not support this in as natural a way so it needs to convert it. See + * https://docs.python.org/2/howto/regex.html#compilation-flags for details. + */ + public void postProcessPattern(String pattern, Map vendorExtensions) { + if (pattern != null) { + int i = pattern.lastIndexOf('/'); + + //Must follow Perl /pattern/modifiers convention + if (pattern.charAt(0) != '/' || i < 2) { + throw new IllegalArgumentException("Pattern must follow the Perl " + + "/pattern/modifiers convention. " + pattern + " is not valid."); + } + + String regex = pattern.substring(1, i).replace("'", "\\'"); + List modifiers = new ArrayList(); + + for (char c : pattern.substring(i).toCharArray()) { + if (regexModifiers.containsKey(c)) { + String modifier = regexModifiers.get(c); + modifiers.add(modifier); + } + } + + vendorExtensions.put("x-regex", regex); + vendorExtensions.put("x-modifiers", modifiers); + } + } + +} diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Rails5ServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RubyOnRailsServerCodegen.java similarity index 98% rename from modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Rails5ServerCodegen.java rename to modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RubyOnRailsServerCodegen.java index abcf121c3e7..7594fb21552 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Rails5ServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RubyOnRailsServerCodegen.java @@ -23,9 +23,9 @@ import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class Rails5ServerCodegen extends DefaultCodegen implements CodegenConfig { +public class RubyOnRailsServerCodegen extends DefaultCodegen implements CodegenConfig { - private static final Logger LOGGER = LoggerFactory.getLogger(Rails5ServerCodegen.class); + private static final Logger LOGGER = LoggerFactory.getLogger(RubyOnRailsServerCodegen.class); private static final SimpleDateFormat MIGRATE_FILE_NAME_FORMAT = new SimpleDateFormat("yyyyMMddHHmmss"); protected String gemName; @@ -58,7 +58,7 @@ public class Rails5ServerCodegen extends DefaultCodegen implements CodegenConfig protected String socketsFolder = tmpFolder + File.separator + "sockets"; protected String vendorFolder = "vendor"; - public Rails5ServerCodegen() { + public RubyOnRailsServerCodegen() { super(); outputFolder = "generated-code" + File.separator + "rails5"; apiPackage = "app/controllers"; 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 6c35a094d35..ec883bb6578 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 @@ -16,9 +16,10 @@ org.openapitools.codegen.languages.ObjcClientCodegen org.openapitools.codegen.languages.PhpClientCodegen org.openapitools.codegen.languages.PowerShellClientCodegen org.openapitools.codegen.languages.PythonClientCodegen +org.openapitools.codegen.languages.PythonFlaskConnexionServerCodegen org.openapitools.codegen.languages.Qt5CPPClientCodegen org.openapitools.codegen.languages.RClientCodegen -org.openapitools.codegen.languages.Rails5ServerCodegen +org.openapitools.codegen.languages.RubyOnRailsServerCodegen org.openapitools.codegen.languages.RubyClientCodegen org.openapitools.codegen.languages.ScalaClientCodegen org.openapitools.codegen.languages.SlimFrameworkServerCodegen From 98a41db17c4635f848cd3dc58537248737f77252 Mon Sep 17 00:00:00 2001 From: wing328 Date: Wed, 28 Mar 2018 16:05:27 +0800 Subject: [PATCH 17/29] fix perl option test, minor reformat --- .../codegen/languages/PerlClientCodegen.java | 11 +++++++++-- .../codegen/languages/RubyOnRailsServerCodegen.java | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PerlClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PerlClientCodegen.java index 2150e110578..74c22ce9a98 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PerlClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PerlClientCodegen.java @@ -101,6 +101,13 @@ public class PerlClientCodegen extends DefaultCodegen implements CodegenConfig { .ENSURE_UNIQUE_PARAMS_DESC).defaultValue(Boolean.TRUE.toString())); cliOptions.add(new CliOption(CodegenConstants.HIDE_GENERATION_TIMESTAMP, CodegenConstants.HIDE_GENERATION_TIMESTAMP_DESC) .defaultValue(Boolean.TRUE.toString())); + + // option to change the order of form/body parameter + cliOptions.add(CliOption.newBoolean( + CodegenConstants.PREPEND_FORM_OR_BODY_PARAMETERS, + CodegenConstants.PREPEND_FORM_OR_BODY_PARAMETERS_DESC) + .defaultValue(Boolean.FALSE.toString())); + } @@ -130,7 +137,7 @@ public class PerlClientCodegen extends DefaultCodegen implements CodegenConfig { additionalProperties.put(CodegenConstants.HIDE_GENERATION_TIMESTAMP, Boolean.TRUE.toString()); } else { additionalProperties.put(CodegenConstants.HIDE_GENERATION_TIMESTAMP, - Boolean.valueOf(additionalProperties().get(CodegenConstants.HIDE_GENERATION_TIMESTAMP).toString())); + Boolean.valueOf(additionalProperties().get(CodegenConstants.HIDE_GENERATION_TIMESTAMP).toString())); } supportingFiles.add(new SupportingFile("ApiClient.mustache", ("lib/" + modulePathPart).replace('/', File.separatorChar), "ApiClient.pm")); @@ -161,7 +168,7 @@ public class PerlClientCodegen extends DefaultCodegen implements CodegenConfig { @Override public String escapeReservedWord(String name) { - if(this.reservedWordsMappings().containsKey(name)) { + if (this.reservedWordsMappings().containsKey(name)) { return this.reservedWordsMappings().get(name); } return "_" + name; diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RubyOnRailsServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RubyOnRailsServerCodegen.java index 7594fb21552..55ea5248836 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RubyOnRailsServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RubyOnRailsServerCodegen.java @@ -182,7 +182,7 @@ public class RubyOnRailsServerCodegen extends DefaultCodegen implements CodegenC @Override public String getHelp() { - return "Generates a Rails5 server library."; + return "Generates a Ruby on Rails server library."; } @Override From 9fa6abd1dad2763d7af557e8c10219354c2d25c5 Mon Sep 17 00:00:00 2001 From: wing328 Date: Wed, 28 Mar 2018 16:58:02 +0800 Subject: [PATCH 18/29] add JS client generator --- .../languages/JavascriptClientCodegen.java | 1053 +++++++++++++++++ .../org.openapitools.codegen.CodegenConfig | 1 + 2 files changed, 1054 insertions(+) create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavascriptClientCodegen.java diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavascriptClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavascriptClientCodegen.java new file mode 100644 index 00000000000..b8761f53ecb --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavascriptClientCodegen.java @@ -0,0 +1,1053 @@ +package org.openapitools.codegen.languages; + +import com.google.common.base.Strings; + +import org.openapitools.codegen.CliOption; +import org.openapitools.codegen.CodegenConfig; +import org.openapitools.codegen.CodegenConstants; +import org.openapitools.codegen.CodegenModel; +import org.openapitools.codegen.CodegenOperation; +import org.openapitools.codegen.CodegenParameter; +import org.openapitools.codegen.CodegenProperty; +import org.openapitools.codegen.CodegenType; +import org.openapitools.codegen.SupportingFile; +import org.openapitools.codegen.DefaultCodegen; +import io.swagger.v3.oas.models.media.*; +import io.swagger.v3.oas.models.PathItem; +import io.swagger.v3.oas.models.PathItem.HttpMethod; +import io.swagger.v3.oas.models.*; +import io.swagger.v3.oas.models.parameters.*; +import io.swagger.v3.oas.models.info.*; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +public class JavascriptClientCodegen extends DefaultCodegen implements CodegenConfig { + @SuppressWarnings("hiding") + private static final Logger LOGGER = LoggerFactory.getLogger(JavascriptClientCodegen.class); + + public static final String PROJECT_NAME = "projectName"; + public static final String MODULE_NAME = "moduleName"; + public static final String PROJECT_DESCRIPTION = "projectDescription"; + public static final String PROJECT_VERSION = "projectVersion"; + public static final String USE_PROMISES = "usePromises"; + public static final String USE_INHERITANCE = "useInheritance"; + public static final String EMIT_MODEL_METHODS = "emitModelMethods"; + public static final String EMIT_JS_DOC = "emitJSDoc"; + public static final String USE_ES6 = "useES6"; + + final String[][] JAVASCRIPT_SUPPORTING_FILES = new String[][]{ + new String[]{"package.mustache", "package.json"}, + new String[]{"index.mustache", "src/index.js"}, + new String[]{"ApiClient.mustache", "src/ApiClient.js"}, + new String[]{"git_push.sh.mustache", "git_push.sh"}, + new String[]{"README.mustache", "README.md"}, + new String[]{"mocha.opts", "mocha.opts"}, + new String[]{"travis.yml", ".travis.yml"} + }; + + final String[][] JAVASCRIPT_ES6_SUPPORTING_FILES = new String[][]{ + new String[]{"package.mustache", "package.json"}, + new String[]{"index.mustache", "src/index.js"}, + new String[]{"ApiClient.mustache", "src/ApiClient.js"}, + new String[]{"git_push.sh.mustache", "git_push.sh"}, + new String[]{"README.mustache", "README.md"}, + new String[]{"mocha.opts", "mocha.opts"}, + new String[]{"travis.yml", ".travis.yml"}, + new String[]{".babelrc.mustache", ".babelrc"} + }; + + protected String projectName; + protected String moduleName; + protected String projectDescription; + protected String projectVersion; + protected String licenseName; + + protected String invokerPackage; + protected String sourceFolder = "src"; + protected String localVariablePrefix = ""; + protected boolean usePromises; + protected boolean emitModelMethods; + protected boolean emitJSDoc = true; + protected String apiDocPath = "docs/"; + protected String modelDocPath = "docs/"; + protected String apiTestPath = "api/"; + protected String modelTestPath = "model/"; + protected boolean useES6 = false; // default is ES5 + + public JavascriptClientCodegen() { + super(); + outputFolder = "generated-code/js"; + modelTemplateFiles.put("model.mustache", ".js"); + modelTestTemplateFiles.put("model_test.mustache", ".js"); + apiTemplateFiles.put("api.mustache", ".js"); + apiTestTemplateFiles.put("api_test.mustache", ".js"); + // subfolder Javascript/es6 + embeddedTemplateDir = templateDir = "Javascript" + File.separator + "es6"; + apiPackage = "api"; + modelPackage = "model"; + modelDocTemplateFiles.put("model_doc.mustache", ".md"); + apiDocTemplateFiles.put("api_doc.mustache", ".md"); + + // reference: http://www.w3schools.com/js/js_reserved.asp + setReservedWordsLowerCase( + Arrays.asList( + "abstract", "arguments", "boolean", "break", "byte", + "case", "catch", "char", "class", "const", + "continue", "debugger", "default", "delete", "do", + "double", "else", "enum", "eval", "export", + "extends", "false", "final", "finally", "float", + "for", "function", "goto", "if", "implements", + "import", "in", "instanceof", "int", "interface", + "let", "long", "native", "new", "null", + "package", "private", "protected", "public", "return", + "short", "static", "super", "switch", "synchronized", + "this", "throw", "throws", "transient", "true", + "try", "typeof", "var", "void", "volatile", + "while", "with", "yield", + "Array", "Date", "eval", "function", "hasOwnProperty", + "Infinity", "isFinite", "isNaN", "isPrototypeOf", + "Math", "NaN", "Number", "Object", + "prototype", "String", "toString", "undefined", "valueOf") + ); + + languageSpecificPrimitives = new HashSet( + Arrays.asList("String", "Boolean", "Number", "Array", "Object", "Date", "File", "Blob") + ); + defaultIncludes = new HashSet(languageSpecificPrimitives); + + instantiationTypes.put("array", "Array"); + instantiationTypes.put("list", "Array"); + instantiationTypes.put("map", "Object"); + typeMapping.clear(); + typeMapping.put("array", "Array"); + typeMapping.put("map", "Object"); + typeMapping.put("List", "Array"); + typeMapping.put("boolean", "Boolean"); + typeMapping.put("string", "String"); + typeMapping.put("int", "Number"); + typeMapping.put("float", "Number"); + typeMapping.put("number", "Number"); + typeMapping.put("DateTime", "Date"); + typeMapping.put("date", "Date"); + typeMapping.put("long", "Number"); + typeMapping.put("short", "Number"); + typeMapping.put("char", "String"); + typeMapping.put("double", "Number"); + typeMapping.put("object", "Object"); + typeMapping.put("integer", "Number"); + // binary not supported in JavaScript client right now, using String as a workaround + typeMapping.put("ByteArray", "Blob"); // I don't see ByteArray defined in the Swagger docs. + typeMapping.put("binary", "Blob"); + typeMapping.put("UUID", "String"); + + importMapping.clear(); + + cliOptions.add(new CliOption(CodegenConstants.SOURCE_FOLDER, CodegenConstants.SOURCE_FOLDER_DESC).defaultValue("src")); + cliOptions.add(new CliOption(CodegenConstants.LOCAL_VARIABLE_PREFIX, CodegenConstants.LOCAL_VARIABLE_PREFIX_DESC)); + cliOptions.add(new CliOption(CodegenConstants.INVOKER_PACKAGE, CodegenConstants.INVOKER_PACKAGE_DESC)); + cliOptions.add(new CliOption(CodegenConstants.API_PACKAGE, CodegenConstants.API_PACKAGE_DESC)); + cliOptions.add(new CliOption(CodegenConstants.MODEL_PACKAGE, CodegenConstants.MODEL_PACKAGE_DESC)); + cliOptions.add(new CliOption(PROJECT_NAME, + "name of the project (Default: generated from info.title or \"swagger-js-client\")")); + cliOptions.add(new CliOption(MODULE_NAME, + "module name for AMD, Node or globals (Default: generated from )")); + cliOptions.add(new CliOption(PROJECT_DESCRIPTION, + "description of the project (Default: using info.description or \"Client library of \")")); + cliOptions.add(new CliOption(PROJECT_VERSION, + "version of the project (Default: using info.version or \"1.0.0\")")); + cliOptions.add(new CliOption(CodegenConstants.LICENSE_NAME, + "name of the license the project uses (Default: using info.license.name)")); + cliOptions.add(new CliOption(USE_PROMISES, + "use Promises as return values from the client API, instead of superagent callbacks") + .defaultValue(Boolean.FALSE.toString())); + cliOptions.add(new CliOption(EMIT_MODEL_METHODS, + "generate getters and setters for model properties") + .defaultValue(Boolean.FALSE.toString())); + cliOptions.add(new CliOption(EMIT_JS_DOC, + "generate JSDoc comments") + .defaultValue(Boolean.TRUE.toString())); + cliOptions.add(new CliOption(USE_INHERITANCE, + "use JavaScript prototype chains & delegation for inheritance") + .defaultValue(Boolean.TRUE.toString())); + cliOptions.add(new CliOption(CodegenConstants.HIDE_GENERATION_TIMESTAMP, "hides the timestamp when files were generated") + .defaultValue(Boolean.TRUE.toString())); + cliOptions.add(new CliOption(USE_ES6, + "use JavaScript ES6 (ECMAScript 6) (beta). Default is ES5.") + .defaultValue(Boolean.FALSE.toString())); + } + + @Override + public CodegenType getTag() { + return CodegenType.CLIENT; + } + + @Override + public String getName() { + return "javascript"; + } + + @Override + public String getHelp() { + return "Generates a Javascript client library."; + } + + @Override + public void processOpts() { + if (additionalProperties.containsKey(USE_ES6)) { + setUseES6(convertPropertyToBooleanAndWriteBack(USE_ES6)); + } else { + setUseES6(false); // default to ES5 + } + super.processOpts(); + + // default HIDE_GENERATION_TIMESTAMP to true + if (!additionalProperties.containsKey(CodegenConstants.HIDE_GENERATION_TIMESTAMP)) { + additionalProperties.put(CodegenConstants.HIDE_GENERATION_TIMESTAMP, Boolean.TRUE.toString()); + } else { + additionalProperties.put(CodegenConstants.HIDE_GENERATION_TIMESTAMP, + Boolean.valueOf(additionalProperties().get(CodegenConstants.HIDE_GENERATION_TIMESTAMP).toString())); + } + + if (additionalProperties.containsKey(PROJECT_NAME)) { + setProjectName(((String) additionalProperties.get(PROJECT_NAME))); + } + if (additionalProperties.containsKey(MODULE_NAME)) { + setModuleName(((String) additionalProperties.get(MODULE_NAME))); + } + if (additionalProperties.containsKey(PROJECT_DESCRIPTION)) { + setProjectDescription(((String) additionalProperties.get(PROJECT_DESCRIPTION))); + } + if (additionalProperties.containsKey(PROJECT_VERSION)) { + setProjectVersion(((String) additionalProperties.get(PROJECT_VERSION))); + } + if (additionalProperties.containsKey(CodegenConstants.LICENSE_NAME)) { + setLicenseName(((String) additionalProperties.get(CodegenConstants.LICENSE_NAME))); + } + if (additionalProperties.containsKey(CodegenConstants.LOCAL_VARIABLE_PREFIX)) { + setLocalVariablePrefix((String) additionalProperties.get(CodegenConstants.LOCAL_VARIABLE_PREFIX)); + } + if (additionalProperties.containsKey(CodegenConstants.SOURCE_FOLDER)) { + setSourceFolder((String) additionalProperties.get(CodegenConstants.SOURCE_FOLDER)); + } + if (additionalProperties.containsKey(CodegenConstants.INVOKER_PACKAGE)) { + setInvokerPackage((String) additionalProperties.get(CodegenConstants.INVOKER_PACKAGE)); + } + if (additionalProperties.containsKey(USE_PROMISES)) { + setUsePromises(convertPropertyToBooleanAndWriteBack(USE_PROMISES)); + } + if (additionalProperties.containsKey(USE_INHERITANCE)) { + setUseInheritance(convertPropertyToBooleanAndWriteBack(USE_INHERITANCE)); + } else { + supportsInheritance = true; + supportsMixins = true; + } + if (additionalProperties.containsKey(EMIT_MODEL_METHODS)) { + setEmitModelMethods(convertPropertyToBooleanAndWriteBack(EMIT_MODEL_METHODS)); + } + if (additionalProperties.containsKey(EMIT_JS_DOC)) { + setEmitJSDoc(convertPropertyToBooleanAndWriteBack(EMIT_JS_DOC)); + } + } + + @Override + public void preprocessOpenAPI(OpenAPI openAPI) { + super.preprocessOpenAPI(openAPI); + + if (openAPI.getInfo() != null) { + Info info = openAPI.getInfo(); + if (StringUtils.isBlank(projectName) && info.getTitle() != null) { + // when projectName is not specified, generate it from info.title + projectName = sanitizeName(dashize(info.getTitle())); + } + if (StringUtils.isBlank(projectVersion)) { + // when projectVersion is not specified, use info.version + projectVersion = escapeUnsafeCharacters(escapeQuotationMark(info.getVersion())); + } + if (projectDescription == null) { + // when projectDescription is not specified, use info.description + projectDescription = sanitizeName(info.getDescription()); + } + + // when licenceName is not specified, use info.license + if (additionalProperties.get(CodegenConstants.LICENSE_NAME) == null && info.getLicense() != null) { + License license = info.getLicense(); + licenseName = license.getName(); + } + } + + // default values + if (StringUtils.isBlank(projectName)) { + projectName = "swagger-js-client"; + } + if (StringUtils.isBlank(moduleName)) { + moduleName = camelize(underscore(projectName)); + } + if (StringUtils.isBlank(projectVersion)) { + projectVersion = "1.0.0"; + } + if (projectDescription == null) { + projectDescription = "Client library of " + projectName; + } + if (StringUtils.isBlank(licenseName)) { + licenseName = "Unlicense"; + } + + additionalProperties.put(PROJECT_NAME, projectName); + additionalProperties.put(MODULE_NAME, moduleName); + additionalProperties.put(PROJECT_DESCRIPTION, escapeText(projectDescription)); + additionalProperties.put(PROJECT_VERSION, projectVersion); + additionalProperties.put(CodegenConstants.LICENSE_NAME, licenseName); + additionalProperties.put(CodegenConstants.API_PACKAGE, apiPackage); + additionalProperties.put(CodegenConstants.INVOKER_PACKAGE, invokerPackage); + additionalProperties.put(CodegenConstants.LOCAL_VARIABLE_PREFIX, localVariablePrefix); + additionalProperties.put(CodegenConstants.MODEL_PACKAGE, modelPackage); + additionalProperties.put(CodegenConstants.SOURCE_FOLDER, sourceFolder); + additionalProperties.put(USE_PROMISES, usePromises); + additionalProperties.put(USE_INHERITANCE, supportsInheritance); + additionalProperties.put(EMIT_MODEL_METHODS, emitModelMethods); + additionalProperties.put(EMIT_JS_DOC, emitJSDoc); + additionalProperties.put(USE_ES6, useES6); + + // make api and model doc path available in mustache template + additionalProperties.put("apiDocPath", apiDocPath); + additionalProperties.put("modelDocPath", modelDocPath); + + String[][] supportingTemplateFiles = JAVASCRIPT_SUPPORTING_FILES; + if (useES6) { + supportingTemplateFiles = JAVASCRIPT_ES6_SUPPORTING_FILES; + } + + for (String[] supportingTemplateFile : supportingTemplateFiles) { + supportingFiles.add(new SupportingFile(supportingTemplateFile[0], "", supportingTemplateFile[1])); + } + } + + @Override + public String escapeReservedWord(String name) { + if (this.reservedWordsMappings().containsKey(name)) { + return this.reservedWordsMappings().get(name); + } + return "_" + name; + } + + /** + * Concatenates an array of path segments into a path string. + * + * @param segments The path segments to concatenate. A segment may contain either of the file separator characters '\' or '/'. + * A segment is ignored if it is null, empty or ".". + * @return A path string using the correct platform-specific file separator character. + */ + private String createPath(String... segments) { + StringBuilder buf = new StringBuilder(); + for (String segment : segments) { + if (!StringUtils.isEmpty(segment) && !segment.equals(".")) { + if (buf.length() != 0) + buf.append(File.separatorChar); + buf.append(segment); + } + } + for (int i = 0; i < buf.length(); i++) { + char c = buf.charAt(i); + if ((c == '/' || c == '\\') && c != File.separatorChar) + buf.setCharAt(i, File.separatorChar); + } + return buf.toString(); + } + + @Override + public String apiTestFileFolder() { + return (outputFolder + "/test/" + apiTestPath).replace('/', File.separatorChar); + } + + @Override + public String modelTestFileFolder() { + return (outputFolder + "/test/" + modelTestPath).replace('/', File.separatorChar); + } + + @Override + public String apiFileFolder() { + return createPath(outputFolder, sourceFolder, invokerPackage, apiPackage()); + } + + @Override + public String modelFileFolder() { + return createPath(outputFolder, sourceFolder, invokerPackage, modelPackage()); + } + + public void setInvokerPackage(String invokerPackage) { + this.invokerPackage = invokerPackage; + } + + public void setSourceFolder(String sourceFolder) { + this.sourceFolder = sourceFolder; + } + + public void setProjectName(String projectName) { + this.projectName = projectName; + } + + public void setLocalVariablePrefix(String localVariablePrefix) { + this.localVariablePrefix = localVariablePrefix; + } + + public void setModuleName(String moduleName) { + this.moduleName = moduleName; + } + + public void setProjectDescription(String projectDescription) { + this.projectDescription = projectDescription; + } + + public void setProjectVersion(String projectVersion) { + this.projectVersion = projectVersion; + } + + public void setLicenseName(String licenseName) { + this.licenseName = licenseName; + } + + public void setUsePromises(boolean usePromises) { + this.usePromises = usePromises; + } + + public void setUseES6(boolean useES6) { + this.useES6 = useES6; + if (useES6) { + embeddedTemplateDir = templateDir = "Javascript/es6"; + LOGGER.info("Using JS ES6 templates"); + } else { + embeddedTemplateDir = templateDir = "Javascript"; + LOGGER.info("Using JS ES5 templates"); + } + } + + public void setUseInheritance(boolean useInheritance) { + this.supportsInheritance = useInheritance; + this.supportsMixins = useInheritance; + } + + public void setEmitModelMethods(boolean emitModelMethods) { + this.emitModelMethods = emitModelMethods; + } + + public void setEmitJSDoc(boolean emitJSDoc) { + this.emitJSDoc = emitJSDoc; + } + + @Override + public String apiDocFileFolder() { + return createPath(outputFolder, apiDocPath); + } + + @Override + public String modelDocFileFolder() { + return createPath(outputFolder, modelDocPath); + } + + @Override + public String toApiDocFilename(String name) { + return toApiName(name); + } + + @Override + public String toModelDocFilename(String name) { + return toModelName(name); + } + + @Override + public String toApiTestFilename(String name) { + return toApiName(name) + ".spec"; + } + + @Override + public String toModelTestFilename(String name) { + return toModelName(name) + ".spec"; + } + + @Override + public String toVarName(String name) { + // sanitize name + name = sanitizeName(name); // FIXME parameter should not be assigned. Also declare it as "final" + + if ("_".equals(name)) { + name = "_u"; + } + + // 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, true); + + // 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); + } + + @Override + public String toModelName(String name) { + name = sanitizeName(name); // FIXME parameter should not be assigned. Also declare it as "final" + + if (!StringUtils.isEmpty(modelNamePrefix)) { + name = modelNamePrefix + "_" + name; + } + + if (!StringUtils.isEmpty(modelNameSuffix)) { + name = name + "_" + modelNameSuffix; + } + + // camelize the model name + // phone_number => PhoneNumber + name = camelize(name); + + // model name cannot use reserved keyword, e.g. return + if (isReservedWord(name)) { + String modelName = "Model" + name; + LOGGER.warn(name + " (reserved word) cannot be used as model name. Renamed to " + modelName); + return modelName; + } + + // model name starts with number + if (name.matches("^\\d.*")) { + String modelName = "Model" + name; // 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 name; + } + + @Override + public String toModelFilename(String name) { + // should be the same as the model name + return toModelName(name); + } + + @Override + public String toModelImport(String name) { + return name; + } + + @Override + public String toApiImport(String name) { + return toApiName(name); + } + + @Override + public String getTypeDeclaration(Schema p) { + if (p instanceof ArraySchema) { + ArraySchema ap = (ArraySchema) p; + Schema inner = ap.getItems(); + return "[" + getTypeDeclaration(inner) + "]"; + } else if (p instanceof MapSchema) { + MapSchema mp = (MapSchema) p; + Schema inner = (Schema) mp.getAdditionalProperties(); + return "{String: " + getTypeDeclaration(inner) + "}"; + } + return super.getTypeDeclaration(p); + } + + @Override + public String toDefaultValue(Schema p) { + if (p instanceof StringSchema) { + StringSchema dp = (StringSchema) p; + if (dp.getDefault() != null) { + return "'" + dp.getDefault() + "'"; + } + } else if (p instanceof BooleanSchema) { + BooleanSchema dp = (BooleanSchema) p; + if (dp.getDefault() != null) { + return dp.getDefault().toString(); + } + } else if (p instanceof DateSchema) { + // TODO + } else if (p instanceof DateTimeSchema) { + // TODO + } else if (p instanceof NumberSchema) { + NumberSchema dp = (NumberSchema) p; + if (dp.getDefault() != null) { + return dp.getDefault().toString(); + } + } else if (p instanceof IntegerSchema) { + IntegerSchema dp = (IntegerSchema) p; + if (dp.getDefault() != null) { + return dp.getDefault().toString(); + } + } + + return null; + } + + @Override + public String toDefaultValueWithParam(String name, Schema p) { + String type = normalizeType(getTypeDeclaration(p)); + if (!StringUtils.isEmpty(p.get$ref())) { + return " = " + type + ".constructFromObject(data['" + p.getName() + "']);"; + } else { + return " = ApiClient.convertToType(data['" + name + "'], " + type + ");"; + } + } + + @Override + public void setParameterExampleValue(CodegenParameter p) { + String example; + + if (p.defaultValue == null) { + example = p.example; + } else { + example = p.defaultValue; + } + + String type = p.baseType; + if (type == null) { + type = p.dataType; + } + + if ("String".equals(type)) { + if (example == null) { + example = p.paramName + "_example"; + } + example = "\"" + escapeText(example) + "\""; + } else if ("Integer".equals(type)) { + if (example == null) { + example = "56"; + } + } else if ("Number".equals(type)) { + if (example == null) { + example = "3.4"; + } + } else if ("Boolean".equals(type)) { + if (example == null) { + example = "true"; + } + } else if ("File".equals(type)) { + if (example == null) { + example = "/path/to/file"; + } + example = "\"" + escapeText(example) + "\""; + } else if ("Date".equals(type)) { + if (example == null) { + example = "2013-10-20T19:20:30+01:00"; + } + example = "new Date(\"" + escapeText(example) + "\")"; + } else if (!languageSpecificPrimitives.contains(type)) { + // type is a model class, e.g. User + example = "new " + moduleName + "." + type + "()"; + } + + if (example == null) { + example = "null"; + } else if (Boolean.TRUE.equals(p.isListContainer)) { + example = "[" + example + "]"; + } else if (Boolean.TRUE.equals(p.isMapContainer)) { + example = "{key: " + example + "}"; + } + + p.example = example; + } + + /** + * Normalize type by wrapping primitive types with single quotes. + * + * @param type Primitive type + * @return Normalized type + */ + public String normalizeType(String type) { + return type.replaceAll("\\b(Boolean|Integer|Number|String|Date|Blob)\\b", "'$1'"); + } + + @Override + public String getSchemaType(Schema p) { + String openAPIType = super.getSchemaType(p); + String type = null; + if (typeMapping.containsKey(openAPIType)) { + type = typeMapping.get(openAPIType); + if (!needToImport(type)) { + return type; + } + } else { + type = openAPIType; + } + if (null == type) { + LOGGER.error("No Type defined for Schema " + p); + } + return toModelName(type); + } + + @Override + public String toOperationId(String operationId) { + // throw exception if method name is empty + if (StringUtils.isEmpty(operationId)) { + throw new RuntimeException("Empty method/operation name (operationId) not allowed"); + } + + operationId = camelize(sanitizeName(operationId), true); + + // method name cannot use reserved keyword, e.g. return + if (isReservedWord(operationId)) { + String newOperationId = camelize("call_" + operationId, true); + LOGGER.warn(operationId + " (reserved word) cannot be used as method name. Renamed to " + newOperationId); + return newOperationId; + } + + return operationId; + } + + @Override + public CodegenOperation fromOperation(String path, String httpMethod, Operation operation, Map definitions, OpenAPI openAPI) { + CodegenOperation op = super.fromOperation(path, httpMethod, operation, definitions, openAPI); + if (op.returnType != null) { + op.returnType = normalizeType(op.returnType); + } + + //path is an unescaped variable in the mustache template api.mustache line 82 '<&path>' + op.path = sanitizePath(op.path); + + // Set vendor-extension to be used in template: + // x-codegen-hasMoreRequired + // x-codegen-hasMoreOptional + // x-codegen-hasRequiredParams + CodegenParameter lastRequired = null; + CodegenParameter lastOptional = null; + for (CodegenParameter p : op.allParams) { + if (p.required) { + lastRequired = p; + } else { + lastOptional = p; + } + } + for (CodegenParameter p : op.allParams) { + if (p == lastRequired) { + p.vendorExtensions.put("x-codegen-hasMoreRequired", false); + } else if (p == lastOptional) { + p.vendorExtensions.put("x-codegen-hasMoreOptional", false); + } else { + p.vendorExtensions.put("x-codegen-hasMoreRequired", true); + p.vendorExtensions.put("x-codegen-hasMoreOptional", true); + } + } + op.vendorExtensions.put("x-codegen-hasRequiredParams", lastRequired != null); + + return op; + } + + @Override + public CodegenModel fromModel(String name, Schema model, Map allDefinitions) { + CodegenModel codegenModel = super.fromModel(name, model, allDefinitions); + + if (allDefinitions != null && codegenModel != null && codegenModel.parent != null && codegenModel.hasEnums) { + final Schema parentModel = allDefinitions.get(codegenModel.parentSchema); + final CodegenModel parentCodegenModel = super.fromModel(codegenModel.parent, parentModel, allDefinitions); + codegenModel = JavascriptClientCodegen.reconcileInlineEnums(codegenModel, parentCodegenModel); + } + if (model instanceof ArraySchema) { + ArraySchema am = (ArraySchema) model; + if (am.getItems() != null) { + codegenModel.getVendorExtensions().put("x-isArray", true); + codegenModel.getVendorExtensions().put("x-itemType", getSchemaType(am.getItems())); + } + } else if (model.getAdditionalProperties() != null) { + if (model.getAdditionalProperties() != null) { + codegenModel.getVendorExtensions().put("x-isMap", true); + codegenModel.getVendorExtensions().put("x-itemType", getSchemaType((Schema) model.getAdditionalProperties())); + } + } + + return codegenModel; + } + + private String sanitizePath(String p) { + //prefer replace a ', instead of a fuLL URL encode for readability + return p.replaceAll("'", "%27"); + } + + private String trimBrackets(String s) { + if (s != null) { + int beginIdx = s.charAt(0) == '[' ? 1 : 0; + int endIdx = s.length(); + if (s.charAt(endIdx - 1) == ']') + endIdx--; + return s.substring(beginIdx, endIdx); + } + return null; + } + + private String getModelledType(String dataType) { + return "module:" + (StringUtils.isEmpty(invokerPackage) ? "" : (invokerPackage + "/")) + + (StringUtils.isEmpty(modelPackage) ? "" : (modelPackage + "/")) + dataType; + } + + private String getJSDocType(CodegenModel cm, CodegenProperty cp) { + if (Boolean.TRUE.equals(cp.isContainer)) { + if (cp.containerType.equals("array")) + return "Array.<" + getJSDocType(cm, cp.items) + ">"; + else if (cp.containerType.equals("map")) + return "Object."; + } + String dataType = trimBrackets(cp.datatypeWithEnum); + if (cp.isEnum) { + dataType = cm.classname + '.' + dataType; + } + if (isModelledType(cp)) + dataType = getModelledType(dataType); + return dataType; + } + + private boolean isModelledType(CodegenProperty cp) { + // N.B. enums count as modelled types, file is not modelled (SuperAgent uses some 3rd party library). + return cp.isEnum || !languageSpecificPrimitives.contains(cp.baseType == null ? cp.datatype : cp.baseType); + } + + private String getJSDocType(CodegenParameter cp) { + String dataType = trimBrackets(cp.dataType); + if (isModelledType(cp)) + dataType = getModelledType(dataType); + if (Boolean.TRUE.equals(cp.isListContainer)) { + return "Array.<" + dataType + ">"; + } else if (Boolean.TRUE.equals(cp.isMapContainer)) { + return "Object."; + } + return dataType; + } + + private boolean isModelledType(CodegenParameter cp) { + // N.B. enums count as modelled types, file is not modelled (SuperAgent uses some 3rd party library). + return cp.isEnum || !languageSpecificPrimitives.contains(cp.baseType == null ? cp.dataType : cp.baseType); + } + + private String getJSDocType(CodegenOperation co) { + String returnType = trimBrackets(co.returnType); + if (returnType != null) { + if (isModelledType(co)) + returnType = getModelledType(returnType); + if (Boolean.TRUE.equals(co.isListContainer)) { + return "Array.<" + returnType + ">"; + } else if (Boolean.TRUE.equals(co.isMapContainer)) { + return "Object."; + } + } + return returnType; + } + + private boolean isModelledType(CodegenOperation co) { + // This seems to be the only way to tell whether an operation return type is modelled. + return !Boolean.TRUE.equals(co.returnTypeIsPrimitive); + } + + @SuppressWarnings("unchecked") + @Override + public Map postProcessOperations(Map objs) { + // Generate and store argument list string of each operation into + // vendor-extension: x-codegen-argList. + Map operations = (Map) objs.get("operations"); + if (operations != null) { + List ops = (List) operations.get("operation"); + for (CodegenOperation operation : ops) { + List argList = new ArrayList(); + boolean hasOptionalParams = false; + for (CodegenParameter p : operation.allParams) { + if (p.required) { + argList.add(p.paramName); + } else { + hasOptionalParams = true; + } + } + if (hasOptionalParams) { + argList.add("opts"); + } + if (!usePromises) { + argList.add("callback"); + } + operation.vendorExtensions.put("x-codegen-argList", StringUtils.join(argList, ", ")); + + // Store JSDoc type specification into vendor-extension: x-jsdoc-type. + for (CodegenParameter cp : operation.allParams) { + String jsdocType = getJSDocType(cp); + cp.vendorExtensions.put("x-jsdoc-type", jsdocType); + } + String jsdocType = getJSDocType(operation); + operation.vendorExtensions.put("x-jsdoc-type", jsdocType); + } + } + return objs; + } + + @SuppressWarnings("unchecked") + @Override + public Map postProcessModels(Map objs) { + objs = super.postProcessModelsEnum(objs); + List models = (List) objs.get("models"); + for (Object _mo : models) { + Map mo = (Map) _mo; + CodegenModel cm = (CodegenModel) mo.get("model"); + + // Collect each model's required property names in *document order*. + // NOTE: can't use 'mandatory' as it is built from ModelImpl.getRequired(), which sorts names + // alphabetically and in any case the document order of 'required' and 'properties' can differ. + List required = new ArrayList<>(); + List allRequired = supportsInheritance || supportsMixins ? new ArrayList() : required; + cm.vendorExtensions.put("x-required", required); + cm.vendorExtensions.put("x-all-required", allRequired); + + for (CodegenProperty var : cm.vars) { + // Add JSDoc @type value for this property. + String jsDocType = getJSDocType(cm, var); + var.vendorExtensions.put("x-jsdoc-type", jsDocType); + + if (Boolean.TRUE.equals(var.required)) { + required.add(var); + } + } + + if (supportsInheritance || supportsMixins) { + for (CodegenProperty var : cm.allVars) { + if (Boolean.TRUE.equals(var.required)) { + allRequired.add(var); + } + } + } + + // set vendor-extension: x-codegen-hasMoreRequired + CodegenProperty lastRequired = null; + for (CodegenProperty var : cm.vars) { + if (var.required) { + lastRequired = var; + } + } + for (CodegenProperty var : cm.vars) { + if (var == lastRequired) { + var.vendorExtensions.put("x-codegen-hasMoreRequired", false); + } else if (var.required) { + var.vendorExtensions.put("x-codegen-hasMoreRequired", true); + } + } + } + return objs; + } + + @Override + protected boolean needToImport(String type) { + return !defaultIncludes.contains(type) + && !languageSpecificPrimitives.contains(type); + } + + private static CodegenModel reconcileInlineEnums(CodegenModel codegenModel, CodegenModel parentCodegenModel) { + // This generator uses inline classes to define enums, which breaks when + // dealing with models that have subTypes. To clean this up, we will analyze + // the parent and child models, look for enums that match, and remove + // them from the child models and leave them in the parent. + // Because the child models extend the parents, the enums will be available via the parent. + + // Only bother with reconciliation if the parent model has enums. + if (parentCodegenModel.hasEnums) { + + // Get the properties for the parent and child models + final List parentModelCodegenProperties = parentCodegenModel.vars; + List codegenProperties = codegenModel.vars; + + // Iterate over all of the parent model properties + boolean removedChildEnum = false; + for (CodegenProperty parentModelCodegenPropery : parentModelCodegenProperties) { + // Look for enums + if (parentModelCodegenPropery.isEnum) { + // Now that we have found an enum in the parent class, + // and search the child class for the same enum. + Iterator iterator = codegenProperties.iterator(); + while (iterator.hasNext()) { + CodegenProperty codegenProperty = iterator.next(); + if (codegenProperty.isEnum && codegenProperty.equals(parentModelCodegenPropery)) { + // We found an enum in the child class that is + // a duplicate of the one in the parent, so remove it. + iterator.remove(); + removedChildEnum = true; + } + } + } + } + + if (removedChildEnum) { + // If we removed an entry from this model's vars, we need to ensure hasMore is updated + int count = 0, numVars = codegenProperties.size(); + for (CodegenProperty codegenProperty : codegenProperties) { + count += 1; + codegenProperty.hasMore = (count < numVars) ? true : false; + } + codegenModel.vars = codegenProperties; + } + } + + return codegenModel; + } + + private static String sanitizePackageName(String packageName) { // FIXME parameter should not be assigned. Also declare it as "final" + packageName = packageName.trim(); + packageName = packageName.replaceAll("[^a-zA-Z0-9_\\.]", "_"); + if (Strings.isNullOrEmpty(packageName)) { + return "invalidPackageName"; + } + return packageName; + } + + @Override + public String toEnumName(CodegenProperty property) { + return sanitizeName(camelize(property.name)) + "Enum"; + } + + @Override + public String toEnumVarName(String value, String datatype) { + if (value.length() == 0) { + return "empty"; + } + + // for symbol, e.g. $, # + if (getSymbolName(value) != null) { + return (getSymbolName(value)).toUpperCase(); + } + + return value; + } + + @Override + public String toEnumValue(String value, String datatype) { + if ("Integer".equals(datatype) || "Number".equals(datatype)) { + return value; + } else { + return "\"" + escapeText(value) + "\""; + } + } + + + @Override + public String escapeQuotationMark(String input) { + // remove ', " to avoid code injection + return input.replace("\"", "").replace("'", ""); + } + + @Override + public String escapeUnsafeCharacters(String input) { + return input.replace("*/", "*_/").replace("/*", "/_*"); + } + +} 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 865151f8011..5f4253ff9c4 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 @@ -10,6 +10,7 @@ org.openapitools.codegen.languages.ElixirClientCodegen org.openapitools.codegen.languages.KotlinClientCodegen org.openapitools.codegen.languages.KotlinServerCodegen org.openapitools.codegen.languages.HaskellServantCodegen +org.openapitools.codegen.languages.JavascriptClientCodegen org.openapitools.codegen.languages.LuaClientCodegen org.openapitools.codegen.languages.LumenServerCodegen org.openapitools.codegen.languages.ObjcClientCodegen From e3da003b1f2c95e130d6c95a192126e9dc01b157 Mon Sep 17 00:00:00 2001 From: wing328 Date: Wed, 28 Mar 2018 18:24:30 +0800 Subject: [PATCH 19/29] add ts angular generator --- .../openapitools/codegen/DefaultCodegen.java | 27 +- .../AbstractTypeScriptClientCodegen.java | 553 ++++++++++++++++++ .../TypeScriptAngularClientCodegen.java | 424 ++++++++++++++ .../org.openapitools.codegen.CodegenConfig | 1 + 4 files changed, 1002 insertions(+), 3 deletions(-) create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractTypeScriptClientCodegen.java create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAngularClientCodegen.java diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java index c054de79fef..dbfdc396a07 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java @@ -1436,7 +1436,7 @@ public class DefaultCodegen implements CodegenConfig { m.allowableValues.put("values", schema.getEnum()); } if (schema.getAdditionalProperties() != null || schema instanceof MapSchema) { - addParentContainer(m, m.name, schema); + addAdditionPropertiesToCodeGenModel(m, schema); } addVars(m, schema.getProperties(), schema.getRequired()); } @@ -1481,6 +1481,10 @@ public class DefaultCodegen implements CodegenConfig { return false; } + protected void addAdditionPropertiesToCodeGenModel(CodegenModel codegenModel, Schema schema) { + addParentContainer(codegenModel, codegenModel.name, schema); + } + protected void addProperties(Map properties, List required, Schema schema, Map allSchemas) { if (schema instanceof ComposedSchema) { ComposedSchema composedSchema = (ComposedSchema) schema; @@ -2467,9 +2471,15 @@ public class DefaultCodegen implements CodegenConfig { setParameterBooleanFlagWithCodegenProperty(codegenParameter, codegenProperty); - codegenParameter.required = codegenProperty.required; - codegenParameter.dataType = codegenProperty.datatype; + String parameterDataType = this.getParameterDataType(parameter, parameterSchema); + if (parameterDataType != null) { + codegenParameter.dataType = parameterDataType; + } else { + codegenParameter.dataType = codegenProperty.datatype; + } codegenParameter.dataFormat = codegenProperty.dataFormat; + codegenParameter.required = codegenProperty.required; + if (codegenProperty.isEnum) { codegenParameter.datatypeWithEnum = codegenProperty.datatypeWithEnum; codegenParameter.enumName = codegenProperty.enumName; @@ -2674,6 +2684,17 @@ public class DefaultCodegen implements CodegenConfig { return codegenParameter; } + /** + * Returns the data type of a parameter. + * Returns null by default to use the CodegenProperty.datatype value + * @param parameter + * @param property + * @return + */ + protected String getParameterDataType(Parameter parameter, Schema schema) { + return null; + } + public boolean isDataTypeBinary(String dataType) { if (dataType != null) { return dataType.toLowerCase().startsWith("byte"); diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractTypeScriptClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractTypeScriptClientCodegen.java new file mode 100644 index 00000000000..23ad7b8d8a9 --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractTypeScriptClientCodegen.java @@ -0,0 +1,553 @@ +package org.openapitools.codegen.languages; + +import java.io.File; +import java.util.*; + +import org.openapitools.codegen.CliOption; +import org.openapitools.codegen.CodegenConfig; +import org.openapitools.codegen.CodegenConstants; +import org.openapitools.codegen.CodegenModel; +import org.openapitools.codegen.CodegenProperty; +import org.openapitools.codegen.CodegenType; +import org.openapitools.codegen.DefaultCodegen; +import io.swagger.v3.oas.models.media.*; +import io.swagger.v3.oas.models.PathItem; +import io.swagger.v3.oas.models.PathItem.HttpMethod; +import io.swagger.v3.oas.models.*; +import io.swagger.v3.oas.models.parameters.*; +import io.swagger.v3.oas.models.info.*; +import io.swagger.v3.parser.util.SchemaTypeUtil; + +import org.apache.commons.lang3.StringUtils; + +public abstract class AbstractTypeScriptClientCodegen extends DefaultCodegen implements CodegenConfig { + private static final String X_DISCRIMINATOR_TYPE = "x-discriminator-value"; + private static final String UNDEFINED_VALUE = "undefined"; + + protected String modelPropertyNaming = "camelCase"; + protected Boolean supportsES6 = true; + protected HashSet languageGenericTypes; + + public AbstractTypeScriptClientCodegen() { + super(); + + // clear import mapping (from default generator) as TS does not use it + // at the moment + importMapping.clear(); + + supportsInheritance = true; + setReservedWordsLowerCase(Arrays.asList( + // local variable names used in API methods (endpoints) + "varLocalPath", "queryParameters", "headerParams", "formParams", "useFormData", "varLocalDeferred", + "requestOptions", + // Typescript reserved words + "abstract", "await", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue", "debugger", "default", "delete", "do", "double", "else", "enum", "export", "extends", "false", "final", "finally", "float", "for", "function", "goto", "if", "implements", "import", "in", "instanceof", "int", "interface", "let", "long", "native", "new", "null", "package", "private", "protected", "public", "return", "short", "static", "super", "switch", "synchronized", "this", "throw", "transient", "true", "try", "typeof", "var", "void", "volatile", "while", "with", "yield")); + + languageSpecificPrimitives = new HashSet<>(Arrays.asList( + "string", + "String", + "boolean", + "Boolean", + "Double", + "Integer", + "Long", + "Float", + "Object", + "Array", + "Date", + "number", + "any", + "File", + "Error", + "Map" + )); + + languageGenericTypes = new HashSet(Arrays.asList( + "Array" + )); + + instantiationTypes.put("array", "Array"); + + typeMapping = new HashMap(); + typeMapping.put("Array", "Array"); + typeMapping.put("array", "Array"); + typeMapping.put("List", "Array"); + typeMapping.put("boolean", "boolean"); + typeMapping.put("string", "string"); + typeMapping.put("int", "number"); + typeMapping.put("float", "number"); + typeMapping.put("number", "number"); + typeMapping.put("long", "number"); + typeMapping.put("short", "number"); + typeMapping.put("char", "string"); + typeMapping.put("double", "number"); + typeMapping.put("object", "any"); + typeMapping.put("integer", "number"); + typeMapping.put("Map", "any"); + typeMapping.put("date", "string"); + typeMapping.put("DateTime", "Date"); + //TODO binary should be mapped to byte array + // mapped to String as a workaround + typeMapping.put("binary", "string"); + typeMapping.put("ByteArray", "string"); + typeMapping.put("UUID", "string"); + typeMapping.put("File", "any"); + typeMapping.put("Error", "Error"); + + cliOptions.add(new CliOption(CodegenConstants.MODEL_PROPERTY_NAMING, CodegenConstants.MODEL_PROPERTY_NAMING_DESC).defaultValue("camelCase")); + cliOptions.add(new CliOption(CodegenConstants.SUPPORTS_ES6, CodegenConstants.SUPPORTS_ES6_DESC).defaultValue("false")); + + } + + @Override + public void processOpts() { + super.processOpts(); + + if (additionalProperties.containsKey(CodegenConstants.MODEL_PROPERTY_NAMING)) { + setModelPropertyNaming((String) additionalProperties.get(CodegenConstants.MODEL_PROPERTY_NAMING)); + } + + if (additionalProperties.containsKey(CodegenConstants.SUPPORTS_ES6)) { + setSupportsES6(Boolean.valueOf(additionalProperties.get(CodegenConstants.SUPPORTS_ES6).toString())); + additionalProperties.put("supportsES6", getSupportsES6()); + } + } + + @Override + public CodegenType getTag() { + return CodegenType.CLIENT; + } + + @Override + public String escapeReservedWord(String name) { + if (this.reservedWordsMappings().containsKey(name)) { + return this.reservedWordsMappings().get(name); + } + return "_" + name; + } + + @Override + public String apiFileFolder() { + return outputFolder + "/" + apiPackage().replace('.', File.separatorChar); + } + + @Override + public String modelFileFolder() { + return outputFolder + "/" + modelPackage().replace('.', File.separatorChar); + } + + @Override + public String toParamName(String name) { + // should be the same as variable name + return toVarName(name); + } + + @Override + public String toVarName(String name) { + // sanitize name + name = sanitizeName(name); + + 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 toModelName(String name) { + name = sanitizeName(name); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. + + if (!StringUtils.isEmpty(modelNamePrefix)) { + name = modelNamePrefix + "_" + name; + } + + if (!StringUtils.isEmpty(modelNameSuffix)) { + name = name + "_" + modelNameSuffix; + } + + // model name cannot use reserved keyword, e.g. return + if (isReservedWord(name)) { + String modelName = camelize("model_" + name); + LOGGER.warn(name + " (reserved word) cannot be used as model name. Renamed to " + modelName); + return modelName; + } + + // model name starts with number + if (name.matches("^\\d.*")) { + String modelName = camelize("model_" + name); // 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; + } + + if (languageSpecificPrimitives.contains(name)) { + String modelName = camelize("model_" + name); + LOGGER.warn(name + " (model name matches existing language type) cannot be used as a model name. Renamed to " + modelName); + return modelName; + } + + // camelize the model name + // phone_number => PhoneNumber + return camelize(name); + } + + @Override + public String toModelFilename(String name) { + // should be the same as the model name + return toModelName(name); + } + + @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 "{ [key: string]: " + getTypeDeclaration(inner) + "; }"; + } else if (p instanceof FileSchema) { + return "any"; + } else if (p instanceof StringSchema && SchemaTypeUtil.BINARY_FORMAT.equals(p.getFormat())) { + return "any"; + } + return super.getTypeDeclaration(p); + } + + + @Override + protected String getParameterDataType(Parameter parameter, Schema p) { + // handle enums of various data types + Schema inner; + if (p instanceof ArraySchema) { + ArraySchema mp1 = (ArraySchema) p; + inner = mp1.getItems(); + return this.getSchemaType(p) + "<" + this.getParameterDataType(parameter, inner) + ">"; + } else if (p instanceof MapSchema) { + MapSchema mp = (MapSchema) p; + inner = (Schema) mp.getAdditionalProperties(); + return "{ [key: string]: " + this.getParameterDataType(parameter, inner) + "; }"; + } else if (p instanceof StringSchema) { + // Handle string enums + StringSchema sp = (StringSchema) p; + if (sp.getEnum() != null) { + return enumValuesToEnumTypeUnion(sp.getEnum(), "string"); + } + } else if (p instanceof IntegerSchema) { + // Handle integer enums + IntegerSchema sp = (IntegerSchema) p; + if (sp.getEnum() != null) { + return numericEnumValuesToEnumTypeUnion(new ArrayList(sp.getEnum())); + } + } else if (p instanceof NumberSchema) { + // Handle double enums + NumberSchema sp = (NumberSchema) p; + if (sp.getEnum() != null) { + return numericEnumValuesToEnumTypeUnion(new ArrayList(sp.getEnum())); + } + } + /* TODO revise the logic below + else if (p instanceof DateSchema) { + // Handle date enums + DateSchema sp = (DateSchema) p; + if (sp.getEnum() != null) { + return enumValuesToEnumTypeUnion(sp.getEnum(), "string"); + } + } else if (p instanceof DateTimeSchema) { + // Handle datetime enums + DateTimeSchema sp = (DateTimeSchema) p; + if (sp.getEnum() != null) { + return enumValuesToEnumTypeUnion(sp.getEnum(), "string"); + } + }*/ + return this.getTypeDeclaration(p); + } + + /** + * Converts a list of strings to a literal union for representing enum values as a type. + * Example output: 'available' | 'pending' | 'sold' + * + * @param values list of allowed enum values + * @param dataType either "string" or "number" + * @return + */ + protected String enumValuesToEnumTypeUnion(List values, String dataType) { + StringBuilder b = new StringBuilder(); + boolean isFirst = true; + for (String value : values) { + if (!isFirst) { + b.append(" | "); + } + b.append(toEnumValue(value.toString(), dataType)); + isFirst = false; + } + return b.toString(); + } + + /** + * Converts a list of numbers to a literal union for representing enum values as a type. + * Example output: 3 | 9 | 55 + * + * @param values + * @return + */ + protected String numericEnumValuesToEnumTypeUnion(List values) { + List stringValues = new ArrayList<>(); + for (Number value : values) { + stringValues.add(value.toString()); + } + return enumValuesToEnumTypeUnion(stringValues, "number"); + } + + @Override + public String toDefaultValue(Schema p) { + if (p instanceof StringSchema) { + StringSchema sp = (StringSchema) p; + if (sp.getDefault() != null) { + return "'" + sp.getDefault() + "'"; + } + return UNDEFINED_VALUE; + } else if (p instanceof BooleanSchema) { + return UNDEFINED_VALUE; + } else if (p instanceof DateSchema) { + return UNDEFINED_VALUE; + } else if (p instanceof DateTimeSchema) { + return UNDEFINED_VALUE; + } else if (p instanceof NumberSchema) { + NumberSchema dp = (NumberSchema) p; + if (dp.getDefault() != null) { + return dp.getDefault().toString(); + } + return UNDEFINED_VALUE; + } else if (p instanceof IntegerSchema) { + IntegerSchema ip = (IntegerSchema) p; + if (ip.getDefault() != null) { + return ip.getDefault().toString(); + } + return UNDEFINED_VALUE; + } else { + return UNDEFINED_VALUE; + } + } + + @Override + public String getSchemaType(Schema p) { + String openAPIType = super.getSchemaType(p); + String type = null; + if (typeMapping.containsKey(openAPIType)) { + type = typeMapping.get(openAPIType); + if (languageSpecificPrimitives.contains(type)) + return type; + } else + type = openAPIType; + return toModelName(type); + } + + @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 + // append _ at the beginning, e.g. _return + if (isReservedWord(operationId)) { + return escapeReservedWord(camelize(sanitizeName(operationId), true)); + } + + return camelize(sanitizeName(operationId), true); + } + + 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; + } + + 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 String toEnumValue(String value, String datatype) { + if ("number".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"; + } + + // for symbol, e.g. $, # + if (getSymbolName(name) != null) { + return camelize(getSymbolName(name)); + } + + // number + if ("number".equals(datatype)) { + String varName = "NUMBER_" + name; + + varName = varName.replaceAll("-", "MINUS_"); + varName = varName.replaceAll("\\+", "PLUS_"); + varName = varName.replaceAll("\\.", "_DOT_"); + return varName; + } + + // string + String enumName = sanitizeName(name); + enumName = enumName.replaceFirst("^_", ""); + enumName = enumName.replaceFirst("_$", ""); + + // camelize the enum variable name + // ref: https://basarat.gitbooks.io/typescript/content/docs/enums.html + enumName = camelize(enumName); + + if (enumName.matches("\\d.*")) { // starts with number + return "_" + enumName; + } else { + return enumName; + } + } + + @Override + public String toEnumName(CodegenProperty property) { + String enumName = toModelName(property.name) + "Enum"; + + if (enumName.matches("\\d.*")) { // starts with number + return "_" + enumName; + } else { + return enumName; + } + } + + @Override + public Map postProcessModels(Map objs) { + // process enum in models + List models = (List) postProcessModelsEnum(objs).get("models"); + for (Object _mo : models) { + Map mo = (Map) _mo; + CodegenModel cm = (CodegenModel) mo.get("model"); + cm.imports = new TreeSet(cm.imports); + // name enum with model name, e.g. StatusEnum => Pet.StatusEnum + for (CodegenProperty var : cm.vars) { + if (Boolean.TRUE.equals(var.isEnum)) { + var.datatypeWithEnum = var.datatypeWithEnum.replace(var.enumName, cm.classname + "." + var.enumName); + } + } + if (cm.parent != null) { + for (CodegenProperty var : cm.allVars) { + if (Boolean.TRUE.equals(var.isEnum)) { + var.datatypeWithEnum = var.datatypeWithEnum + .replace(var.enumName, cm.classname + "." + var.enumName); + } + } + } + } + + return objs; + } + + @Override + public Map postProcessAllModels(Map objs) { + Map result = super.postProcessAllModels(objs); + + for (Map.Entry entry : result.entrySet()) { + Map inner = (Map) entry.getValue(); + List> models = (List>) inner.get("models"); + for (Map mo : models) { + CodegenModel cm = (CodegenModel) mo.get("model"); + if (cm.discriminator != null && cm.children != null) { + for (CodegenModel child : cm.children) { + this.setDiscriminatorValue(child, cm.discriminator.getPropertyName(), this.getDiscriminatorValue(child)); + } + } + } + } + return result; + } + + public void setSupportsES6(Boolean value) { + supportsES6 = value; + } + + public Boolean getSupportsES6() { + return supportsES6; + } + + private void setDiscriminatorValue(CodegenModel model, String baseName, String value) { + for (CodegenProperty prop : model.allVars) { + if (prop.baseName.equals(baseName)) { + prop.discriminatorValue = value; + } + } + if (model.children != null) { + final boolean newDiscriminator = model.discriminator != null; + for (CodegenModel child : model.children) { + this.setDiscriminatorValue(child, baseName, newDiscriminator ? value : this.getDiscriminatorValue(child)); + } + } + } + + private String getDiscriminatorValue(CodegenModel model) { + return model.vendorExtensions.containsKey(X_DISCRIMINATOR_TYPE) ? + (String) model.vendorExtensions.get(X_DISCRIMINATOR_TYPE) : model.classname; + } + + @Override + public String escapeQuotationMark(String input) { + // remove ', " to avoid code injection + return input.replace("\"", "").replace("'", ""); + } + + @Override + public String escapeUnsafeCharacters(String input) { + return input.replace("*/", "*_/").replace("/*", "/_*"); + } +} diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAngularClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAngularClientCodegen.java new file mode 100644 index 00000000000..21ed141b5f7 --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAngularClientCodegen.java @@ -0,0 +1,424 @@ +package org.openapitools.codegen.languages; + +import java.io.File; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import io.swagger.v3.parser.util.SchemaTypeUtil; +import org.apache.commons.lang3.StringUtils; +import org.openapitools.codegen.CliOption; +import org.openapitools.codegen.CodegenModel; +import org.openapitools.codegen.CodegenParameter; +import org.openapitools.codegen.CodegenOperation; +import org.openapitools.codegen.SupportingFile; +import org.openapitools.codegen.utils.SemVer; + +import io.swagger.v3.oas.models.media.*; +import io.swagger.v3.oas.models.PathItem; +import io.swagger.v3.oas.models.PathItem.HttpMethod; +import io.swagger.v3.oas.models.*; +import io.swagger.v3.oas.models.parameters.*; +import io.swagger.v3.oas.models.info.*; + +public class TypeScriptAngularClientCodegen extends AbstractTypeScriptClientCodegen { + private static final SimpleDateFormat SNAPSHOT_SUFFIX_FORMAT = new SimpleDateFormat("yyyyMMddHHmm"); + private static final String X_DISCRIMINATOR_TYPE = "x-discriminator-value"; + + public static final String NPM_NAME = "npmName"; + public static final String NPM_VERSION = "npmVersion"; + public static final String NPM_REPOSITORY = "npmRepository"; + public static final String SNAPSHOT = "snapshot"; + public static final String WITH_INTERFACES = "withInterfaces"; + public static final String TAGGED_UNIONS = "taggedUnions"; + public static final String NG_VERSION = "ngVersion"; + + protected String npmName = null; + protected String npmVersion = "1.0.0"; + protected String npmRepository = null; + + private boolean taggedUnions = false; + + public TypeScriptAngularClientCodegen() { + super(); + this.outputFolder = "generated-code/typescript-angular"; + + embeddedTemplateDir = templateDir = "typescript-angular"; + modelTemplateFiles.put("model.mustache", ".ts"); + apiTemplateFiles.put("api.service.mustache", ".ts"); + languageSpecificPrimitives.add("Blob"); + typeMapping.put("file", "Blob"); + apiPackage = "api"; + modelPackage = "model"; + + this.cliOptions.add(new CliOption(NPM_NAME, "The name under which you want to publish generated npm package")); + this.cliOptions.add(new CliOption(NPM_VERSION, "The version of your npm package")); + this.cliOptions.add(new CliOption(NPM_REPOSITORY, + "Use this property to set an url your private npmRepo in the package.json")); + this.cliOptions.add(new CliOption(SNAPSHOT, + "When setting this property to true the version will be suffixed with -SNAPSHOT.yyyyMMddHHmm", + SchemaTypeUtil.BOOLEAN_TYPE).defaultValue(Boolean.FALSE.toString())); + this.cliOptions.add(new CliOption(WITH_INTERFACES, + "Setting this property to true will generate interfaces next to the default class implementations.", + SchemaTypeUtil.BOOLEAN_TYPE).defaultValue(Boolean.FALSE.toString())); + this.cliOptions.add(new CliOption(TAGGED_UNIONS, + "Use discriminators to create tagged unions instead of extending interfaces.", + SchemaTypeUtil.BOOLEAN_TYPE).defaultValue(Boolean.FALSE.toString())); + this.cliOptions.add(new CliOption(NG_VERSION, "The version of Angular. Default is '4.3'")); + } + + @Override + protected void addAdditionPropertiesToCodeGenModel(CodegenModel codegenModel, Schema schema) { + codegenModel.additionalPropertiesType = getTypeDeclaration((Schema) schema.getAdditionalProperties()); + addImport(codegenModel, codegenModel.additionalPropertiesType); + } + + @Override + public String getName() { + return "typescript-angular"; + } + + @Override + public String getHelp() { + return "Generates a TypeScript Angular (2.x or 4.x) client library."; + } + + @Override + public void processOpts() { + super.processOpts(); + supportingFiles.add( + new SupportingFile("models.mustache", modelPackage().replace('.', File.separatorChar), "models.ts")); + supportingFiles + .add(new SupportingFile("apis.mustache", apiPackage().replace('.', File.separatorChar), "api.ts")); + supportingFiles.add(new SupportingFile("index.mustache", getIndexDirectory(), "index.ts")); + supportingFiles.add(new SupportingFile("api.module.mustache", getIndexDirectory(), "api.module.ts")); + supportingFiles.add(new SupportingFile("configuration.mustache", getIndexDirectory(), "configuration.ts")); + supportingFiles.add(new SupportingFile("variables.mustache", getIndexDirectory(), "variables.ts")); + supportingFiles.add(new SupportingFile("encoder.mustache", getIndexDirectory(), "encoder.ts")); + supportingFiles.add(new SupportingFile("gitignore", "", ".gitignore")); + supportingFiles.add(new SupportingFile("git_push.sh.mustache", "", "git_push.sh")); + supportingFiles.add(new SupportingFile("README.mustache", getIndexDirectory(), "README.md")); + + if (additionalProperties.containsKey(NPM_NAME)) { + addNpmPackageGeneration(); + } + + if (additionalProperties.containsKey(WITH_INTERFACES)) { + boolean withInterfaces = Boolean.parseBoolean(additionalProperties.get(WITH_INTERFACES).toString()); + if (withInterfaces) { + apiTemplateFiles.put("apiInterface.mustache", "Interface.ts"); + } + } + + if (additionalProperties.containsKey(TAGGED_UNIONS)) { + taggedUnions = Boolean.parseBoolean(additionalProperties.get(TAGGED_UNIONS).toString()); + } + + // determine NG version + SemVer ngVersion; + if (additionalProperties.containsKey(NG_VERSION)) { + ngVersion = new SemVer(additionalProperties.get(NG_VERSION).toString()); + } else { + ngVersion = new SemVer("4.3.0"); + LOGGER.info("generating code for Angular {} ...", ngVersion); + LOGGER.info(" (you can select the angular version by setting the additionalProperty ngVersion)"); + } + additionalProperties.put(NG_VERSION, ngVersion); + additionalProperties.put("injectionToken", ngVersion.atLeast("4.0.0") ? "InjectionToken" : "OpaqueToken"); + additionalProperties.put("injectionTokenTyped", ngVersion.atLeast("4.0.0")); + additionalProperties.put("useHttpClient", ngVersion.atLeast("4.3.0")); + if (!ngVersion.atLeast("4.3.0")) { + supportingFiles.add(new SupportingFile("rxjs-operators.mustache", getIndexDirectory(), "rxjs-operators.ts")); + } + } + + private void addNpmPackageGeneration() { + if (additionalProperties.containsKey(NPM_NAME)) { + this.setNpmName(additionalProperties.get(NPM_NAME).toString()); + } + + if (additionalProperties.containsKey(NPM_VERSION)) { + this.setNpmVersion(additionalProperties.get(NPM_VERSION).toString()); + } + + if (additionalProperties.containsKey(SNAPSHOT) + && Boolean.valueOf(additionalProperties.get(SNAPSHOT).toString())) { + this.setNpmVersion(npmVersion + "-SNAPSHOT." + SNAPSHOT_SUFFIX_FORMAT.format(new Date())); + } + additionalProperties.put(NPM_VERSION, npmVersion); + + if (additionalProperties.containsKey(NPM_REPOSITORY)) { + this.setNpmRepository(additionalProperties.get(NPM_REPOSITORY).toString()); + } + + //Files for building our lib + supportingFiles.add(new SupportingFile("package.mustache", getIndexDirectory(), "package.json")); + supportingFiles.add(new SupportingFile("typings.mustache", getIndexDirectory(), "typings.json")); + supportingFiles.add(new SupportingFile("tsconfig.mustache", getIndexDirectory(), "tsconfig.json")); + } + + private String getIndexDirectory() { + String indexPackage = modelPackage.substring(0, Math.max(0, modelPackage.lastIndexOf('.'))); + return indexPackage.replace('.', File.separatorChar); + } + + @Override + public boolean isDataTypeFile(final String dataType) { + return dataType != null && dataType.equals("Blob"); + } + + @Override + public String getTypeDeclaration(Schema p) { + if (p instanceof FileSchema) { + return "Blob"; + } else if (!StringUtils.isEmpty(p.get$ref())) { + return "any"; + } else { + return super.getTypeDeclaration(p); + } + } + + + @Override + public String getSchemaType(Schema p) { + String openAPIType = super.getSchemaType(p); + if (isLanguagePrimitive(openAPIType) || isLanguageGenericType(openAPIType)) { + return openAPIType; + } + applyLocalTypeMapping(openAPIType); + return openAPIType; + } + + private String applyLocalTypeMapping(String type) { + if (typeMapping.containsKey(type)) { + type = typeMapping.get(type); + } + return type; + } + + private boolean isLanguagePrimitive(String type) { + return languageSpecificPrimitives.contains(type); + } + + private boolean isLanguageGenericType(String type) { + for (String genericType : languageGenericTypes) { + if (type.startsWith(genericType + "<")) { + return true; + } + } + return false; + } + + @Override + public void postProcessParameter(CodegenParameter parameter) { + super.postProcessParameter(parameter); + parameter.dataType = applyLocalTypeMapping(parameter.dataType); + } + + @Override + public Map postProcessOperations(Map operations) { + Map objs = (Map) operations.get("operations"); + + // Add filename information for api imports + objs.put("apiFilename", getApiFilenameFromClassname(objs.get("classname").toString())); + + List ops = (List) objs.get("operation"); + for (CodegenOperation op : ops) { + if ((boolean) additionalProperties.get("useHttpClient")) { + op.httpMethod = op.httpMethod.toLowerCase(Locale.ENGLISH); + } else { + // Convert httpMethod to Angular's RequestMethod enum + // https://angular.io/docs/ts/latest/api/http/index/RequestMethod-enum.html + switch (op.httpMethod) { + case "GET": + op.httpMethod = "RequestMethod.Get"; + break; + case "POST": + op.httpMethod = "RequestMethod.Post"; + break; + case "PUT": + op.httpMethod = "RequestMethod.Put"; + break; + case "DELETE": + op.httpMethod = "RequestMethod.Delete"; + break; + case "OPTIONS": + op.httpMethod = "RequestMethod.Options"; + break; + case "HEAD": + op.httpMethod = "RequestMethod.Head"; + break; + case "PATCH": + op.httpMethod = "RequestMethod.Patch"; + break; + default: + throw new RuntimeException("Unknown HTTP Method " + op.httpMethod + " not allowed"); + } + } + + // Prep a string buffer where we're going to set up our new version of the string. + StringBuilder pathBuffer = new StringBuilder(); + StringBuilder parameterName = new StringBuilder(); + int insideCurly = 0; + + // Iterate through existing string, one character at a time. + for (int i = 0; i < op.path.length(); i++) { + switch (op.path.charAt(i)) { + case '{': + // We entered curly braces, so track that. + insideCurly++; + + // Add the more complicated component instead of just the brace. + pathBuffer.append("${encodeURIComponent(String("); + break; + case '}': + // We exited curly braces, so track that. + insideCurly--; + + // Add the more complicated component instead of just the brace. + pathBuffer.append(toVarName(parameterName.toString())); + pathBuffer.append("))}"); + parameterName.setLength(0); + break; + default: + if (insideCurly > 0) { + parameterName.append(op.path.charAt(i)); + } else { + pathBuffer.append(op.path.charAt(i)); + } + break; + } + } + + // Overwrite path to TypeScript template string, after applying everything we just did. + op.path = pathBuffer.toString(); + } + + // Add additional filename information for model imports in the services + List> imports = (List>) operations.get("imports"); + for (Map im : imports) { + im.put("filename", im.get("import")); + im.put("classname", getModelnameFromModelFilename(im.get("filename").toString())); + } + + return operations; + } + + @Override + public Map postProcessModels(Map objs) { + Map result = super.postProcessModels(objs); + + return postProcessModelsEnum(result); + } + + @Override + public Map postProcessAllModels(Map objs) { + Map result = super.postProcessAllModels(objs); + + for (Map.Entry entry : result.entrySet()) { + Map inner = (Map) entry.getValue(); + List> models = (List>) inner.get("models"); + for (Map mo : models) { + CodegenModel cm = (CodegenModel) mo.get("model"); + if (taggedUnions) { + mo.put(TAGGED_UNIONS, true); + if (cm.discriminator != null && cm.children != null) { + for (CodegenModel child : cm.children) { + cm.imports.add(child.classname); + } + } + if (cm.parent != null) { + cm.imports.remove(cm.parent); + } + } + // Add additional filename information for imports + mo.put("tsImports", toTsImports(cm, cm.imports)); + } + } + return result; + } + + private List> toTsImports(CodegenModel cm, Set imports) { + List> tsImports = new ArrayList<>(); + for (String im : imports) { + if (!im.equals(cm.classname)) { + HashMap tsImport = new HashMap<>(); + tsImport.put("classname", im); + tsImport.put("filename", toModelFilename(im)); + tsImports.add(tsImport); + } + } + return tsImports; + } + + @Override + public String toApiName(String name) { + if (name.length() == 0) { + return "DefaultService"; + } + return initialCaps(name) + "Service"; + } + + @Override + public String toApiFilename(String name) { + if (name.length() == 0) { + return "default.service"; + } + return camelize(name, true) + ".service"; + } + + @Override + public String toApiImport(String name) { + return apiPackage() + "/" + toApiFilename(name); + } + + @Override + public String toModelFilename(String name) { + return camelize(toModelName(name), true); + } + + @Override + public String toModelImport(String name) { + return modelPackage() + "/" + toModelFilename(name); + } + + public String getNpmName() { + return npmName; + } + + public void setNpmName(String npmName) { + this.npmName = npmName; + } + + public String getNpmVersion() { + return npmVersion; + } + + public void setNpmVersion(String npmVersion) { + this.npmVersion = npmVersion; + } + + public String getNpmRepository() { + return npmRepository; + } + + public void setNpmRepository(String npmRepository) { + this.npmRepository = npmRepository; + } + + private String getApiFilenameFromClassname(String classname) { + String name = classname.substring(0, classname.length() - "Service".length()); + return toApiFilename(name); + } + + private String getModelnameFromModelFilename(String filename) { + String name = filename.substring((modelPackage() + "/").length()); + return camelize(name); + } + +} 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 5f4253ff9c4..375e6e0256a 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 @@ -28,3 +28,4 @@ org.openapitools.codegen.languages.SlimFrameworkServerCodegen org.openapitools.codegen.languages.SilexServerCodegen org.openapitools.codegen.languages.SinatraServerCodegen org.openapitools.codegen.languages.TizenClientCodegen +org.openapitools.codegen.languages.TypeScriptAngularClientCodegen From 4d0bdf8abaf3059c33822c65841a604352eab85f Mon Sep 17 00:00:00 2001 From: wing328 Date: Thu, 29 Mar 2018 00:24:33 +0800 Subject: [PATCH 20/29] remove debug logging --- .../openapitools/codegen/DefaultCodegen.java | 27 +++++-------------- .../codegen/DefaultGenerator.java | 2 +- .../languages/JavascriptClientCodegen.java | 1 + 3 files changed, 8 insertions(+), 22 deletions(-) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java index dbfdc396a07..7d48f0d1986 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java @@ -1446,7 +1446,7 @@ public class DefaultCodegen implements CodegenConfig { postProcessModelProperty(m, prop); } } - LOGGER.info("debugging fromModel return: " + m); + LOGGER.debug("debugging fromModel return: " + m); return m; } @@ -1536,7 +1536,7 @@ public class DefaultCodegen implements CodegenConfig { LOGGER.error("Unexpected missing property for name " + name); return null; } - LOGGER.info("debugging fromProperty for " + name + " : " + p); + LOGGER.debug("debugging fromProperty for " + name + " : " + p); CodegenProperty property = CodegenModelFactory.newInstance(CodegenModelType.PROPERTY); property.name = toVarName(name); @@ -1624,7 +1624,6 @@ public class DefaultCodegen implements CodegenConfig { if (p instanceof BinarySchema || SchemaTypeUtil.BINARY_FORMAT.equals(p.getFormat())) { property.isBinary = true; } else if (p instanceof FileSchema) { - LOGGER.info("debugging FileSchema: " + property.baseName); property.isFile = true; } else { property.maxLength = p.getMaxLength(); @@ -1781,7 +1780,7 @@ public class DefaultCodegen implements CodegenConfig { setNonArrayMapProperty(property, type); } - LOGGER.info("debugging from property : " + property); + LOGGER.debug("debugging from property return: " + property); return property; } @@ -2116,14 +2115,12 @@ public class DefaultCodegen implements CodegenConfig { // add form parameters to the beginning of all parameter list if (prependFormOrBodyParameters) { for (CodegenParameter cp : formParams) { - LOGGER.info("Adding " + cp.baseName + " to allParams list"); allParams.add(cp.copy()); } } } else { // process body parameter if (StringUtils.isNotBlank(requestBody.get$ref())) { - LOGGER.info("request body name: " + getSimpleRef(requestBody.get$ref())); requestBody = openAPI.getComponents().getRequestBodies().get(getSimpleRef(requestBody.get$ref())); } bodyParam = fromRequestBody(requestBody, schemas, imports); @@ -2142,8 +2139,6 @@ public class DefaultCodegen implements CodegenConfig { if (parameters != null) { for (Parameter param : parameters) { - LOGGER.info("parameter debugging " + param.getName() + " => " + param); - if (StringUtils.isNotBlank(param.get$ref())) { param = getParameterFromRef(param.get$ref(), openAPI); } @@ -2460,7 +2455,6 @@ public class DefaultCodegen implements CodegenConfig { } CodegenProperty codegenProperty = fromProperty(parameter.getName(), parameterSchema); - LOGGER.info("fromProperty debug required: " + parameter.getRequired()); // TODO revise below which seems not working //if (parameterSchema.getRequired() != null && !parameterSchema.getRequired().isEmpty() && parameterSchema.getRequired().contains(codegenProperty.baseName)) { codegenProperty.required = Boolean.TRUE.equals(parameter.getRequired()) ? true : false; @@ -2680,7 +2674,7 @@ public class DefaultCodegen implements CodegenConfig { setParameterExampleValue(codegenParameter); postProcessParameter(codegenParameter); - LOGGER.info("debugging codegenParameter return: " + codegenParameter); + LOGGER.debug("debugging codegenParameter return: " + codegenParameter); return codegenParameter; } @@ -3766,11 +3760,8 @@ public class DefaultCodegen implements CodegenConfig { scopes.add(scope); } - //LOGGER.info("setOauth2Info setting scopes properly"); codegenSecurity.scopes = scopes; } - - //LOGGER.info("setOauth2Info scope: " + flow.getScopes()); } private List getInterfaces(ComposedSchema composed) { @@ -3876,15 +3867,12 @@ public class DefaultCodegen implements CodegenConfig { protected String getParentName(ComposedSchema composedSchema, Map allSchemas) { if (composedSchema.getAllOf() != null && !composedSchema.getAllOf().isEmpty()) { - LOGGER.info("debugging getParentName if: " + composedSchema); Schema schema = composedSchema.getAllOf().get(0); String ref = schema.get$ref(); if (StringUtils.isBlank(ref)) { return null; } return getSimpleRef(ref); - } else { - LOGGER.info("debugging getParentName else: " + composedSchema); } return null; } @@ -3937,7 +3925,7 @@ public class DefaultCodegen implements CodegenConfig { public List fromRequestBodyToFormParameters(RequestBody body, Map schemas, Set imports) { List parameters = new ArrayList(); - LOGGER.info("Request body = " + body); + LOGGER.debug("debugging fromRequestBodyToFormParameters= " + body); Schema schema = getSchemaFromBody(body); if (schema.getProperties() != null && !schema.getProperties().isEmpty()) { Map properties = schema.getProperties(); @@ -3961,7 +3949,7 @@ public class DefaultCodegen implements CodegenConfig { public CodegenParameter fromFormProperty(String name, Schema propertySchema, Set imports) { CodegenParameter codegenParameter = CodegenModelFactory.newInstance(CodegenModelType.PARAMETER); - LOGGER.info("Debugging fromRequestBodyToFormParameters: " + name); + LOGGER.debug("Debugging fromFormProperty: " + name); CodegenProperty codegenProperty = fromProperty(name, propertySchema); codegenParameter.isFormParam = Boolean.TRUE; @@ -4064,7 +4052,6 @@ public class DefaultCodegen implements CodegenConfig { codegenParameter.baseType = codegenModel.classname; codegenParameter.dataType = getTypeDeclaration(codegenModel.classname); codegenParameter.description = codegenModel.description; - LOGGER.info("debugging fromRequestBody imports model: " + codegenParameter); imports.add(codegenParameter.baseType); } else { CodegenProperty codegenProperty = fromProperty("property", schema); @@ -4072,10 +4059,8 @@ public class DefaultCodegen implements CodegenConfig { codegenParameter.baseType = codegenProperty.baseType; codegenParameter.dataType = codegenProperty.datatype; codegenParameter.description = codegenProperty.description; - LOGGER.info("Setting description to body parameter: " + codegenProperty.description); if (codegenProperty.complexType != null) { - LOGGER.info("debugging fromRequestBody imports: " + codegenProperty.complexType); imports.add(codegenProperty.complexType); } } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java index f7376e8e099..2a89b711c04 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java @@ -359,7 +359,7 @@ public class DefaultGenerator extends AbstractGenerator implements Generator { try { //don't generate models that have an import mapping if (config.importMapping().containsKey(name)) { - LOGGER.info("Model " + name + " not imported due to import mapping"); + LOGGER.debug("Model " + name + " not imported due to import mapping"); continue; } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavascriptClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavascriptClientCodegen.java index b8761f53ecb..6377ffd6b2e 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavascriptClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavascriptClientCodegen.java @@ -146,6 +146,7 @@ public class JavascriptClientCodegen extends DefaultCodegen implements CodegenCo typeMapping.put("object", "Object"); typeMapping.put("integer", "Number"); // binary not supported in JavaScript client right now, using String as a workaround + // TODO revise the logic below typeMapping.put("ByteArray", "Blob"); // I don't see ByteArray defined in the Swagger docs. typeMapping.put("binary", "Blob"); typeMapping.put("UUID", "String"); From b41626d1e8278891f7000534fbcf14fe25a0a91c Mon Sep 17 00:00:00 2001 From: wing328 Date: Thu, 29 Mar 2018 11:28:21 +0800 Subject: [PATCH 21/29] rename php sever geneator, add php ze-ph generator --- .../openapitools/codegen/DefaultCodegen.java | 4 - ...odegen.java => PhpLumenServerCodegen.java} | 35 +- ...odegen.java => PhpSilexServerCodegen.java} | 16 +- ...Codegen.java => PhpSlimServerCodegen.java} | 24 +- ...endExpressivePathHandlerServerCodegen.java | 336 ++++++++++++++++++ .../org.openapitools.codegen.CodegenConfig | 7 +- .../codegen/lumen/LumenServerOptionsTest.java | 51 --- .../lumen/PhpLumenServerOptionsTest.java | 51 +++ ...st.java => PhpSilexServerOptionsTest.java} | 14 +- ...est.java => PhpSlimServerOptionsTest.java} | 14 +- ...ava => PhpLumenServerOptionsProvider.java} | 2 +- ...ava => PhpSilexServerOptionsProvider.java} | 2 +- ...java => PhpSlimServerOptionsProvider.java} | 2 +- 13 files changed, 446 insertions(+), 112 deletions(-) rename modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/{LumenServerCodegen.java => PhpLumenServerCodegen.java} (88%) rename modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/{SilexServerCodegen.java => PhpSilexServerCodegen.java} (96%) rename modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/{SlimFrameworkServerCodegen.java => PhpSlimServerCodegen.java} (92%) create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PhpZendExpressivePathHandlerServerCodegen.java delete mode 100644 modules/openapi-generator/src/test/java/org/openapitools/codegen/lumen/LumenServerOptionsTest.java create mode 100644 modules/openapi-generator/src/test/java/org/openapitools/codegen/lumen/PhpLumenServerOptionsTest.java rename modules/openapi-generator/src/test/java/org/openapitools/codegen/silex/{SilexServerOptionsTest.java => PhpSilexServerOptionsTest.java} (57%) rename modules/openapi-generator/src/test/java/org/openapitools/codegen/slim/{SlimFrameworkServerOptionsTest.java => PhpSlimServerOptionsTest.java} (55%) rename modules/openapi-generator/src/test/java/org/openapitools/options/{LumenServerOptionsProvider.java => PhpLumenServerOptionsProvider.java} (97%) rename modules/openapi-generator/src/test/java/org/openapitools/options/{SilexServerOptionsProvider.java => PhpSilexServerOptionsProvider.java} (94%) rename modules/openapi-generator/src/test/java/org/openapitools/options/{SlimFrameworkServerOptionsProvider.java => PhpSlimServerOptionsProvider.java} (93%) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java index 7d48f0d1986..fd0b6e9179c 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java @@ -3939,10 +3939,6 @@ public class DefaultCodegen implements CodegenConfig { } } - for (int i = 0; i < parameters.size(); i++) { - System.out.println("parmaeter name:" + parameters.get(i).baseName); - } - return parameters; } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/LumenServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PhpLumenServerCodegen.java similarity index 88% rename from modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/LumenServerCodegen.java rename to modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PhpLumenServerCodegen.java index 78513fd0bca..17084db375a 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/LumenServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PhpLumenServerCodegen.java @@ -10,16 +10,15 @@ import java.util.HashMap; import java.util.Map; import java.util.TreeMap; -public class LumenServerCodegen extends AbstractPhpCodegen -{ - @SuppressWarnings("hiding") +public class PhpLumenServerCodegen extends AbstractPhpCodegen { + @SuppressWarnings("hiding") protected String apiVersion = "1.0.0"; - + /** * Configures the type of generator. - * - * @return the CodegenType for this generator - * @see org.openapitools.codegen.CodegenType + * + * @return the CodegenType for this generator + * @see org.openapitools.codegen.CodegenType */ public CodegenType getTag() { return CodegenType.SERVER; @@ -28,7 +27,7 @@ public class LumenServerCodegen extends AbstractPhpCodegen /** * 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 */ public String getName() { @@ -38,14 +37,14 @@ public class LumenServerCodegen extends AbstractPhpCodegen /** * Returns human-friendly help for the generator. Provide the consumer with help * tips, parameters here - * + * * @return A string value for the help message */ public String getHelp() { return "Generates a PHP Lumen server library."; } - public LumenServerCodegen() { + public PhpLumenServerCodegen() { super(); embeddedTemplateDir = templateDir = "lumen"; @@ -88,13 +87,13 @@ public class LumenServerCodegen extends AbstractPhpCodegen supportingFiles.add(new SupportingFile("app.php", packagePath + File.separator + srcBasePath + File.separator + "bootstrap", "app.php")); supportingFiles.add(new SupportingFile("index.php", packagePath + File.separator + srcBasePath + File.separator + "public", "index.php")); supportingFiles.add(new SupportingFile("User.php", packagePath + File.separator + srcBasePath + File.separator + "app", "User.php")); - supportingFiles.add(new SupportingFile("Kernel.php", packagePath + File.separator + srcBasePath + File.separator + "app" + File.separator + "Console", "Kernel.php")); - supportingFiles.add(new SupportingFile("Handler.php", packagePath + File.separator + srcBasePath + File.separator + "app" + File.separator + "Exceptions", "Handler.php")); - supportingFiles.add(new SupportingFile("routes.mustache", packagePath + File.separator + srcBasePath + File.separator + "app" + File.separator + "Http", "routes.php")); - - supportingFiles.add(new SupportingFile("Controller.php", packagePath + File.separator + srcBasePath + File.separator + "app" + File.separator + "Http" + File.separator + "Controllers" + File.separator, "Controller.php")); - supportingFiles.add(new SupportingFile("Authenticate.php", packagePath + File.separator + srcBasePath + File.separator + "app" + File.separator + "Http" + File.separator + "Middleware" + File.separator, "Authenticate.php")); - + supportingFiles.add(new SupportingFile("Kernel.php", packagePath + File.separator + srcBasePath + File.separator + "app" + File.separator + "Console", "Kernel.php")); + supportingFiles.add(new SupportingFile("Handler.php", packagePath + File.separator + srcBasePath + File.separator + "app" + File.separator + "Exceptions", "Handler.php")); + supportingFiles.add(new SupportingFile("routes.mustache", packagePath + File.separator + srcBasePath + File.separator + "app" + File.separator + "Http", "routes.php")); + + supportingFiles.add(new SupportingFile("Controller.php", packagePath + File.separator + srcBasePath + File.separator + "app" + File.separator + "Http" + File.separator + "Controllers" + File.separator, "Controller.php")); + supportingFiles.add(new SupportingFile("Authenticate.php", packagePath + File.separator + srcBasePath + File.separator + "app" + File.separator + "Http" + File.separator + "Middleware" + File.separator, "Authenticate.php")); + } // override with any special post-processing @@ -113,7 +112,7 @@ public class LumenServerCodegen extends AbstractPhpCodegen throw new IllegalArgumentException("'.' (dot) is not supported by PHP Lumen. Please refer to https://github.com/swagger-api/swagger-codegen/issues/6897 for more info."); } } - + // sort the endpoints in ascending to avoid the route priority issure. // https://github.com/swagger-api/swagger-codegen/issues/2643 Collections.sort(operations, new Comparator() { diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SilexServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PhpSilexServerCodegen.java similarity index 96% rename from modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SilexServerCodegen.java rename to modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PhpSilexServerCodegen.java index aad5a466f01..7c4741e45ad 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SilexServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PhpSilexServerCodegen.java @@ -20,13 +20,13 @@ import java.util.Map; import org.apache.commons.lang3.StringUtils; -public class SilexServerCodegen extends DefaultCodegen implements CodegenConfig { +public class PhpSilexServerCodegen extends DefaultCodegen implements CodegenConfig { protected String invokerPackage; protected String groupId = "io.swagger"; protected String artifactId = "swagger-server"; protected String artifactVersion = "1.0.0"; - public SilexServerCodegen() { + public PhpSilexServerCodegen() { super(); invokerPackage = camelize("SwaggerServer"); @@ -103,7 +103,9 @@ public class SilexServerCodegen extends DefaultCodegen implements CodegenConfig } @Override - public String getName() { + public String getName() + + { return "php-silex"; } @@ -113,13 +115,13 @@ public class SilexServerCodegen extends DefaultCodegen implements CodegenConfig } @Override - public String escapeReservedWord(String name) { - if(this.reservedWordsMappings().containsKey(name)) { + public String escapeReservedWord(String name) { + if (this.reservedWordsMappings().containsKey(name)) { return this.reservedWordsMappings().get(name); } return "_" + name; } - + @Override public String apiFileFolder() { return (outputFolder + "/" + apiPackage()).replace('/', File.separatorChar); @@ -233,7 +235,7 @@ public class SilexServerCodegen extends DefaultCodegen implements CodegenConfig for (int i = 0; i < items.length; ++i) { if (items[i].matches("^\\{(.*)\\}$")) { // wrap in {} // camelize path variable - items[i] = "{" + camelize(items[i].substring(1, items[i].length()-1), true) + "}"; + items[i] = "{" + camelize(items[i].substring(1, items[i].length() - 1), true) + "}"; } } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SlimFrameworkServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PhpSlimServerCodegen.java similarity index 92% rename from modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SlimFrameworkServerCodegen.java rename to modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PhpSlimServerCodegen.java index 0d7e38eaed8..6d0a195693c 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SlimFrameworkServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PhpSlimServerCodegen.java @@ -18,7 +18,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.regex.Matcher; -public class SlimFrameworkServerCodegen extends DefaultCodegen implements CodegenConfig { +public class PhpSlimServerCodegen extends DefaultCodegen implements CodegenConfig { protected String invokerPackage; protected String srcBasePath = "lib"; protected String groupId = "io.swagger"; @@ -29,7 +29,7 @@ public class SlimFrameworkServerCodegen extends DefaultCodegen implements Codege private String variableNamingConvention = "camelCase"; - public SlimFrameworkServerCodegen() { + public PhpSlimServerCodegen() { super(); // clear import mapping (from default generator) as slim does not use it @@ -120,8 +120,8 @@ public class SlimFrameworkServerCodegen extends DefaultCodegen implements Codege } @Override - public String escapeReservedWord(String name) { - if(this.reservedWordsMappings().containsKey(name)) { + public String escapeReservedWord(String name) { + if (this.reservedWordsMappings().containsKey(name)) { return this.reservedWordsMappings().get(name); } return "_" + name; @@ -199,11 +199,11 @@ public class SlimFrameworkServerCodegen extends DefaultCodegen implements Codege if ("camelCase".equals(variableNamingConvention)) { // return the name in camelCase style // phone_number => phoneNumber - name = camelize(name, true); + name = camelize(name, true); } else { // default to snake case // return the name in underscore style // PhoneNumber => phone_number - name = underscore(name); + name = underscore(name); } // parameter name starting with number won't compile @@ -260,12 +260,12 @@ public class SlimFrameworkServerCodegen extends DefaultCodegen implements Codege } return (getPackagePath() + File.separatorChar + basePath - // Replace period, backslash, forward slash with file separator in package name - + packageName.replaceAll("[\\.\\\\/]", Matcher.quoteReplacement(File.separator)) - // Trim prefix file separators from package path - .replaceAll(regFirstPathSeparator, "")) - // Trim trailing file separators from the overall path - .replaceAll(regLastPathSeparator+ "$", ""); + // Replace period, backslash, forward slash with file separator in package name + + packageName.replaceAll("[\\.\\\\/]", Matcher.quoteReplacement(File.separator)) + // Trim prefix file separators from package path + .replaceAll(regFirstPathSeparator, "")) + // Trim trailing file separators from the overall path + .replaceAll(regLastPathSeparator + "$", ""); } public String getPackagePath() { diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PhpZendExpressivePathHandlerServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PhpZendExpressivePathHandlerServerCodegen.java new file mode 100644 index 00000000000..6186919fa85 --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PhpZendExpressivePathHandlerServerCodegen.java @@ -0,0 +1,336 @@ +package org.openapitools.codegen.languages; + +import io.swagger.v3.oas.models.PathItem; +import org.openapitools.codegen.*; + +import io.swagger.v3.oas.models.PathItem.HttpMethod; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.media.*; +import io.swagger.v3.core.util.Yaml; +import io.swagger.v3.oas.models.parameters.*; + + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class PhpZendExpressivePathHandlerServerCodegen extends AbstractPhpCodegen { + + public static final String VEN_FROM_QUERY = "internal.ze-ph.fromQuery"; + public static final String VEN_COLLECTION_FORMAT = "internal.ze-ph.collectionFormat"; + public static final String VEN_QUERY_DATA_TYPE = "internal.ze-ph.queryDataType"; + public static final String VEN_HAS_QUERY_DATA = "internal.ze-ph.hasQueryData"; + + @Override + public CodegenType getTag() { + return CodegenType.SERVER; + } + + @Override + public String getName() { + return "php-ze-ph"; + } + + @Override + public String getHelp() { + return "Generates PHP server stub using Zend Expressive ( https://zendframework.github.io/zend-expressive ) and Path Handler ( https://github.com/Articus/PathHandler )."; + } + + public PhpZendExpressivePathHandlerServerCodegen() { + super(); + //no point to use double - http://php.net/manual/en/language.types.float.php , especially because of PHP 7+ float type declaration + typeMapping.put("double", "float"); + + embeddedTemplateDir = templateDir = "ze-ph"; + invokerPackage = "App"; + packagePath = ""; + srcBasePath = "src" + File.separator + "App"; + apiDirName = "Handler"; + modelDirName = "DTO"; + apiPackage = invokerPackage + "\\" + apiDirName; + modelPackage = invokerPackage + "\\" + modelDirName; + + apiTestTemplateFiles.clear(); + modelTestTemplateFiles.clear(); + apiDocTemplateFiles.clear(); + modelDocTemplateFiles.clear(); + + supportingFiles.add(new SupportingFile("README.md.mustache", packagePath, "README.md")); + supportingFiles.add(new SupportingFile("composer.json.mustache", packagePath, "composer.json")); + supportingFiles.add(new SupportingFile("index.php", packagePath + File.separator + "public", "index.php")); + supportingFiles.add(new SupportingFile("container.php", packagePath + File.separator + "application", "container.php")); + supportingFiles.add(new SupportingFile("config.yml", packagePath + File.separator + "application", "config.yml")); + supportingFiles.add(new SupportingFile("app.yml.mustache", packagePath + File.separator + "application" + File.separator + "config", "app.yml")); + supportingFiles.add(new SupportingFile("path_handler.yml.mustache", packagePath + File.separator + "application" + File.separator + "config", "path_handler.yml")); + supportingFiles.add(new SupportingFile("data_transfer.yml.mustache", packagePath + File.separator + "application" + File.separator + "config", "data_transfer.yml")); + supportingFiles.add(new SupportingFile("ErrorMiddleware.php.mustache", packagePath + File.separator + srcBasePath, "ErrorMiddleware.php")); + supportingFiles.add(new SupportingFile("Date.php.mustache", packagePath + File.separator + srcBasePath + File.separator + "Strategy", "Date.php")); + supportingFiles.add(new SupportingFile("DateTime.php.mustache", packagePath + File.separator + srcBasePath + File.separator + "Strategy", "DateTime.php")); + supportingFiles.add(new SupportingFile("QueryParameter.php.mustache", packagePath + File.separator + srcBasePath + File.separator + "Strategy", "QueryParameter.php")); + supportingFiles.add(new SupportingFile("QueryParameterArray.php.mustache", packagePath + File.separator + srcBasePath + File.separator + "Strategy", "QueryParameterArray.php")); + supportingFiles.add(new SupportingFile("Type.php.mustache", packagePath + File.separator + srcBasePath + File.separator + "Validator", "Type.php")); + supportingFiles.add(new SupportingFile("QueryParameterType.php.mustache", packagePath + File.separator + srcBasePath + File.separator + "Validator", "QueryParameterType.php")); + supportingFiles.add(new SupportingFile("QueryParameterArrayType.php.mustache", packagePath + File.separator + srcBasePath + File.separator + "Validator", "QueryParameterArrayType.php")); + + additionalProperties.put(CodegenConstants.ARTIFACT_VERSION, "1.0.0"); + } + + /** + * Add operation to group + * Override of default grouping - group by resource path, not tag + * + * @param tag name of the tag + * @param resourcePath path of the resource + * @param operation Swagger Operation object + * @param co Codegen Operation object + * @param operations map of Codegen operations + */ + @Override + public void addOperationToGroup(String tag, String resourcePath, Operation operation, CodegenOperation co, Map> operations) { + List opList = operations.get(resourcePath); + if (opList == null) { + opList = new ArrayList(); + operations.put(resourcePath, opList); + } + //ignore duplicate operation ids - that means that operation has several tags + int counter = 0; + for (CodegenOperation op : opList) { + if (co.operationId.equals(op.operationId)) { + counter++; + } + } + if (counter == 0) { + co.operationIdLowerCase = co.operationId.toLowerCase(); + opList.add(co); + co.baseName = tag; + } + } + + /** + * Return the file name of the Api Test + * + * @param name the file name of the Api + * @return the file name of the Api + */ + @Override + public String toApiFilename(String name) { + return toApiName(name); + } + + /** + * Output the API (class) name (capitalized) ending with "Api" + * Return DefaultApi if name is empty + * + * @param name the name of the Api + * @return capitalized Api name ending with "Api" + */ + @Override + public String toApiName(String name) { + //Remove } + name = name.replaceAll("[\\}]", ""); + return super.toModelName(name); + } + + /** + * Generate additional model definitions from query parameters + * + * @param openAPI OpenAPI object + */ + @Override + public void preprocessOpenAPI(OpenAPI openAPI) { + super.preprocessOpenAPI(openAPI); + + Map paths = openAPI.getPaths(); + if (paths != null) { + for (String pathname : paths.keySet()) { + PathItem path = paths.get(pathname); + Map operationMap = path.readOperationsMap(); + if (operationMap != null) { + for (HttpMethod method : operationMap.keySet()) { + Operation operation = operationMap.get(method); + Map schemas = new HashMap<>(); + if (operation == null || operation.getParameters() == null) { + continue; + } + + for (Parameter parameter : operation.getParameters()) { + Schema schema = convertParameterToSchema(parameter); + if (schema != null) { + schemas.put(schema.getName(), schema); + } + } + + if (!schemas.isEmpty()) { + Schema model = new Schema(); + String operationId = getOrGenerateOperationId(operation, pathname, method.name()); + model.setDescription("Query parameters for " + operationId); + model.setProperties(schemas); + model.getExtensions().put(VEN_FROM_QUERY, Boolean.TRUE); + String definitionName = generateUniqueDefinitionName(operationId + "QueryData", openAPI); + openAPI.getComponents().addSchemas(definitionName, model); + String definitionModel = "\\" + modelPackage + "\\" + toModelName(definitionName); + operation.getExtensions().put(VEN_QUERY_DATA_TYPE, definitionModel); + operation.getExtensions().put(VEN_HAS_QUERY_DATA, Boolean.TRUE); + } + } + } + } + } + } + + protected Schema convertParameterToSchema(Parameter parameter) { + Schema property = null; + if (parameter instanceof QueryParameter) { + QueryParameter queryParameter = (QueryParameter) parameter; + // array + if (queryParameter.getSchema() instanceof ArraySchema) { + Schema inner = ((ArraySchema) queryParameter.getSchema()).getItems(); + ArraySchema arraySchema = new ArraySchema(); + arraySchema.setMinItems(queryParameter.getSchema().getMinItems()); + arraySchema.setMaxItems(queryParameter.getSchema().getMaxItems()); + arraySchema.setItems(inner); + String collectionFormat = getCollectionFormat(queryParameter); + if (collectionFormat == null) { + collectionFormat = "csv"; + } + arraySchema.getExtensions().put(VEN_COLLECTION_FORMAT, collectionFormat); + property = arraySchema; + } else { // non-array e.g. string, integer + switch (queryParameter.getSchema().getType()) { + case "string": + StringSchema stringSchema = new StringSchema(); + stringSchema.setMinLength(queryParameter.getSchema().getMinLength()); + stringSchema.setMaxLength(queryParameter.getSchema().getMaxLength()); + stringSchema.setPattern(queryParameter.getSchema().getPattern()); + stringSchema.setEnum(queryParameter.getSchema().getEnum()); + property = stringSchema; + break; + case "integer": + IntegerSchema integerSchema = new IntegerSchema(); + integerSchema.setMinimum(queryParameter.getSchema().getMinimum()); + integerSchema.setMaximum(queryParameter.getSchema().getMaximum()); + property = integerSchema; + break; + case "number": + NumberSchema floatSchema = new NumberSchema(); + floatSchema.setMinimum(queryParameter.getSchema().getMinimum()); + floatSchema.setMaximum(queryParameter.getSchema().getMaximum()); + property = floatSchema; + break; + case "boolean": + property = new BooleanSchema(); + break; + case "date": + property = new DateSchema(); + break; + case "date-time": + property = new DateTimeSchema(); + break; + } + } + if (property != null) { + property.setName(queryParameter.getName()); + property.setDescription(queryParameter.getDescription()); + if (Boolean.TRUE.equals(queryParameter.getRequired())) { + List required = new ArrayList(); + required.add(queryParameter.getName()); + } + + property.getExtensions().put(VEN_FROM_QUERY, Boolean.TRUE); + } + } + return property; + } + + protected String generateUniqueDefinitionName(String name, OpenAPI openAPI) { + String result = name; + if (openAPI.getComponents().getSchemas() != null) { + int count = 1; + while (openAPI.getComponents().getSchemas().containsKey(result)) { + result = name + "_" + count; + count += 1; + } + } + return result; + } + + @Override + public Map postProcessOperations(Map objs) { + objs = super.postProcessOperations(objs); + Map operations = (Map) objs.get("operations"); + List operationList = (List) operations.get("operation"); + String interfaceToImplement; + StringBuilder interfacesToImplement = new StringBuilder(); + String classMethod; + String pathPattern = null; + for (CodegenOperation op : operationList) { + switch (op.httpMethod) { + case "GET": + interfaceToImplement = "Operation\\GetInterface"; + classMethod = "handleGet"; + break; + case "POST": + interfaceToImplement = "Operation\\PostInterface"; + classMethod = "handlePost"; + break; + case "PATCH": + interfaceToImplement = "Operation\\PatchInterface"; + classMethod = "handlePatch"; + break; + case "PUT": + interfaceToImplement = "Operation\\PutInterface"; + classMethod = "handlePut"; + break; + case "DELETE": + interfaceToImplement = "Operation\\DeleteInterface"; + classMethod = "handleDelete"; + break; + default: + throw new RuntimeException("Unknown HTTP Method " + op.httpMethod + " not allowed"); + } + if (interfacesToImplement.length() > 0) { + interfacesToImplement.append(", "); + } + interfacesToImplement.append(interfaceToImplement); + op.httpMethod = classMethod; + //All operations have same path because of custom operation grouping, so path pattern can be calculated only once + if (pathPattern == null) { + pathPattern = generatePathPattern(op); + } + } + operations.put("interfacesToImplement", interfacesToImplement.toString()); + operations.put("pathPattern", pathPattern); + + return objs; + } + + protected String generatePathPattern(CodegenOperation op) { + String result = op.path; + for (CodegenParameter pp : op.pathParams) { + StringBuilder replacement = new StringBuilder("{" + pp.paramName); + if (pp.isEnum) { + StringBuilder enumRegExp = new StringBuilder(); + for (String enumValue : pp._enum) { + if (enumRegExp.length() > 0) { + enumRegExp.append("|"); + } + enumRegExp.append(enumValue.replaceAll("[\\Q<>()[]{}|^$-=!?*+.\\\\E]", "\\\\$0")); + } + replacement.append(":"); + replacement.append(enumRegExp); + } else if (pp.isInteger) { + replacement.append(":0|(?:-?[1-9][0-9]*)"); + } else if (pp.isString && (pp.pattern != null) && (!pp.pattern.isEmpty())) { + replacement.append(":"); + replacement.append(pp.pattern); + } + //TODO add regular expressions for other types if they are actually used for path parameters + replacement.append("}"); + result = result.replace("{" + pp.paramName + "}", replacement); + } + return result; + } +} 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 375e6e0256a..757146defcb 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 @@ -12,7 +12,10 @@ org.openapitools.codegen.languages.KotlinServerCodegen org.openapitools.codegen.languages.HaskellServantCodegen org.openapitools.codegen.languages.JavascriptClientCodegen org.openapitools.codegen.languages.LuaClientCodegen -org.openapitools.codegen.languages.LumenServerCodegen +org.openapitools.codegen.languages.PhpLumenServerCodegen +org.openapitools.codegen.languages.PhpSlimServerCodegen +org.openapitools.codegen.languages.PhpSilexServerCodegen +org.openapitools.codegen.languages.PhpZendExpressivePathHandlerServerCodegen org.openapitools.codegen.languages.ObjcClientCodegen org.openapitools.codegen.languages.PerlClientCodegen org.openapitools.codegen.languages.PhpClientCodegen @@ -24,8 +27,6 @@ org.openapitools.codegen.languages.RClientCodegen org.openapitools.codegen.languages.RubyOnRailsServerCodegen 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 org.openapitools.codegen.languages.TizenClientCodegen org.openapitools.codegen.languages.TypeScriptAngularClientCodegen diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/lumen/LumenServerOptionsTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/lumen/LumenServerOptionsTest.java deleted file mode 100644 index 9a270837251..00000000000 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/lumen/LumenServerOptionsTest.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.openapitools.codegen.lumen; - -import org.openapitools.codegen.AbstractOptionsTest; -import org.openapitools.codegen.CodegenConfig; -import org.openapitools.codegen.languages.LumenServerCodegen; -import org.openapitools.codegen.options.LumenServerOptionsProvider; - -import mockit.Expectations; -import mockit.Tested; - -public class LumenServerOptionsTest extends AbstractOptionsTest { - - @Tested - private LumenServerCodegen clientCodegen; - - public LumenServerOptionsTest() { - super(new LumenServerOptionsProvider()); - } - - @Override - protected CodegenConfig getCodegenConfig() { - return clientCodegen; - } - - @SuppressWarnings("unused") - @Override - protected void setExpectations() { - new Expectations(clientCodegen) {{ - clientCodegen.setSortParamsByRequiredFlag(Boolean.valueOf(LumenServerOptionsProvider.SORT_PARAMS_VALUE)); - times = 1; - clientCodegen.setParameterNamingConvention(LumenServerOptionsProvider.VARIABLE_NAMING_CONVENTION_VALUE); - clientCodegen.setModelPackage(LumenServerOptionsProvider.MODEL_PACKAGE_VALUE); - times = 1; - clientCodegen.setApiPackage(LumenServerOptionsProvider.API_PACKAGE_VALUE); - times = 1; - times = 1; - clientCodegen.setInvokerPackage(LumenServerOptionsProvider.INVOKER_PACKAGE_VALUE); - times = 1; - clientCodegen.setPackagePath(LumenServerOptionsProvider.PACKAGE_PATH_VALUE); - times = 1; - clientCodegen.setSrcBasePath(LumenServerOptionsProvider.SRC_BASE_PATH_VALUE); - times = 1; - clientCodegen.setGitUserId(LumenServerOptionsProvider.GIT_USER_ID_VALUE); - times = 1; - clientCodegen.setGitRepoId(LumenServerOptionsProvider.GIT_REPO_ID_VALUE); - times = 1; - clientCodegen.setArtifactVersion(LumenServerOptionsProvider.ARTIFACT_VERSION_VALUE); - times = 1; - }}; - } -} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/lumen/PhpLumenServerOptionsTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/lumen/PhpLumenServerOptionsTest.java new file mode 100644 index 00000000000..cf29430f578 --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/lumen/PhpLumenServerOptionsTest.java @@ -0,0 +1,51 @@ +package org.openapitools.codegen.lumen; + +import org.openapitools.codegen.AbstractOptionsTest; +import org.openapitools.codegen.CodegenConfig; +import org.openapitools.codegen.languages.PhpLumenServerCodegen; +import org.openapitools.codegen.options.PhpLumenServerOptionsProvider; + +import mockit.Expectations; +import mockit.Tested; + +public class PhpLumenServerOptionsTest extends AbstractOptionsTest { + + @Tested + private PhpLumenServerCodegen clientCodegen; + + public PhpLumenServerOptionsTest() { + super(new PhpLumenServerOptionsProvider()); + } + + @Override + protected CodegenConfig getCodegenConfig() { + return clientCodegen; + } + + @SuppressWarnings("unused") + @Override + protected void setExpectations() { + new Expectations(clientCodegen) {{ + clientCodegen.setSortParamsByRequiredFlag(Boolean.valueOf(PhpLumenServerOptionsProvider.SORT_PARAMS_VALUE)); + times = 1; + clientCodegen.setParameterNamingConvention(PhpLumenServerOptionsProvider.VARIABLE_NAMING_CONVENTION_VALUE); + clientCodegen.setModelPackage(PhpLumenServerOptionsProvider.MODEL_PACKAGE_VALUE); + times = 1; + clientCodegen.setApiPackage(PhpLumenServerOptionsProvider.API_PACKAGE_VALUE); + times = 1; + times = 1; + clientCodegen.setInvokerPackage(PhpLumenServerOptionsProvider.INVOKER_PACKAGE_VALUE); + times = 1; + clientCodegen.setPackagePath(PhpLumenServerOptionsProvider.PACKAGE_PATH_VALUE); + times = 1; + clientCodegen.setSrcBasePath(PhpLumenServerOptionsProvider.SRC_BASE_PATH_VALUE); + times = 1; + clientCodegen.setGitUserId(PhpLumenServerOptionsProvider.GIT_USER_ID_VALUE); + times = 1; + clientCodegen.setGitRepoId(PhpLumenServerOptionsProvider.GIT_REPO_ID_VALUE); + times = 1; + clientCodegen.setArtifactVersion(PhpLumenServerOptionsProvider.ARTIFACT_VERSION_VALUE); + times = 1; + }}; + } +} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/silex/SilexServerOptionsTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/silex/PhpSilexServerOptionsTest.java similarity index 57% rename from modules/openapi-generator/src/test/java/org/openapitools/codegen/silex/SilexServerOptionsTest.java rename to modules/openapi-generator/src/test/java/org/openapitools/codegen/silex/PhpSilexServerOptionsTest.java index 3f8ac24de59..c453d1a4ab1 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/silex/SilexServerOptionsTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/silex/PhpSilexServerOptionsTest.java @@ -2,19 +2,19 @@ package org.openapitools.codegen.silex; import org.openapitools.codegen.AbstractOptionsTest; import org.openapitools.codegen.CodegenConfig; -import org.openapitools.codegen.languages.SilexServerCodegen; -import org.openapitools.codegen.options.SilexServerOptionsProvider; +import org.openapitools.codegen.languages.PhpSilexServerCodegen; +import org.openapitools.codegen.options.PhpSilexServerOptionsProvider; import mockit.Expectations; import mockit.Tested; -public class SilexServerOptionsTest extends AbstractOptionsTest { +public class PhpSilexServerOptionsTest extends AbstractOptionsTest { @Tested - private SilexServerCodegen clientCodegen; + private PhpSilexServerCodegen clientCodegen; - public SilexServerOptionsTest() { - super(new SilexServerOptionsProvider()); + public PhpSilexServerOptionsTest() { + super(new PhpSilexServerOptionsProvider()); } @Override @@ -26,7 +26,7 @@ public class SilexServerOptionsTest extends AbstractOptionsTest { @Override protected void setExpectations() { new Expectations(clientCodegen) {{ - clientCodegen.setSortParamsByRequiredFlag(Boolean.valueOf(SilexServerOptionsProvider.SORT_PARAMS_VALUE)); + clientCodegen.setSortParamsByRequiredFlag(Boolean.valueOf(PhpSilexServerOptionsProvider.SORT_PARAMS_VALUE)); times = 1; }}; } diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/slim/SlimFrameworkServerOptionsTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/slim/PhpSlimServerOptionsTest.java similarity index 55% rename from modules/openapi-generator/src/test/java/org/openapitools/codegen/slim/SlimFrameworkServerOptionsTest.java rename to modules/openapi-generator/src/test/java/org/openapitools/codegen/slim/PhpSlimServerOptionsTest.java index 6e2e06e3a67..4730fceae2b 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/slim/SlimFrameworkServerOptionsTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/slim/PhpSlimServerOptionsTest.java @@ -2,19 +2,19 @@ package org.openapitools.codegen.slim; import org.openapitools.codegen.AbstractOptionsTest; import org.openapitools.codegen.CodegenConfig; -import org.openapitools.codegen.languages.SlimFrameworkServerCodegen; -import org.openapitools.codegen.options.SlimFrameworkServerOptionsProvider; +import org.openapitools.codegen.languages.PhpSlimServerCodegen; +import org.openapitools.codegen.options.PhpSlimServerOptionsProvider; import mockit.Expectations; import mockit.Tested; -public class SlimFrameworkServerOptionsTest extends AbstractOptionsTest { +public class PhpSlimServerOptionsTest extends AbstractOptionsTest { @Tested - private SlimFrameworkServerCodegen clientCodegen; + private PhpSlimServerCodegen clientCodegen; - public SlimFrameworkServerOptionsTest() { - super(new SlimFrameworkServerOptionsProvider()); + public PhpSlimServerOptionsTest() { + super(new PhpSlimServerOptionsProvider()); } @Override @@ -26,7 +26,7 @@ public class SlimFrameworkServerOptionsTest extends AbstractOptionsTest { @Override protected void setExpectations() { new Expectations(clientCodegen) {{ - clientCodegen.setSortParamsByRequiredFlag(Boolean.valueOf(SlimFrameworkServerOptionsProvider.SORT_PARAMS_VALUE)); + clientCodegen.setSortParamsByRequiredFlag(Boolean.valueOf(PhpSlimServerOptionsProvider.SORT_PARAMS_VALUE)); times = 1; }}; } diff --git a/modules/openapi-generator/src/test/java/org/openapitools/options/LumenServerOptionsProvider.java b/modules/openapi-generator/src/test/java/org/openapitools/options/PhpLumenServerOptionsProvider.java similarity index 97% rename from modules/openapi-generator/src/test/java/org/openapitools/options/LumenServerOptionsProvider.java rename to modules/openapi-generator/src/test/java/org/openapitools/options/PhpLumenServerOptionsProvider.java index ad7e7d8fef5..3ec1980c3e7 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/options/LumenServerOptionsProvider.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/options/PhpLumenServerOptionsProvider.java @@ -7,7 +7,7 @@ import com.google.common.collect.ImmutableMap; import java.util.Map; -public class LumenServerOptionsProvider implements OptionsProvider { +public class PhpLumenServerOptionsProvider implements OptionsProvider { public static final String MODEL_PACKAGE_VALUE = "package"; public static final String API_PACKAGE_VALUE = "apiPackage"; public static final String SORT_PARAMS_VALUE = "false"; diff --git a/modules/openapi-generator/src/test/java/org/openapitools/options/SilexServerOptionsProvider.java b/modules/openapi-generator/src/test/java/org/openapitools/options/PhpSilexServerOptionsProvider.java similarity index 94% rename from modules/openapi-generator/src/test/java/org/openapitools/options/SilexServerOptionsProvider.java rename to modules/openapi-generator/src/test/java/org/openapitools/options/PhpSilexServerOptionsProvider.java index de4a9202611..9ef424f6680 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/options/SilexServerOptionsProvider.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/options/PhpSilexServerOptionsProvider.java @@ -6,7 +6,7 @@ import com.google.common.collect.ImmutableMap; import java.util.Map; -public class SilexServerOptionsProvider implements OptionsProvider { +public class PhpSilexServerOptionsProvider implements OptionsProvider { public static final String SORT_PARAMS_VALUE = "false"; public static final String ENSURE_UNIQUE_PARAMS_VALUE = "true"; public static final String ALLOW_UNICODE_IDENTIFIERS_VALUE = "false"; diff --git a/modules/openapi-generator/src/test/java/org/openapitools/options/SlimFrameworkServerOptionsProvider.java b/modules/openapi-generator/src/test/java/org/openapitools/options/PhpSlimServerOptionsProvider.java similarity index 93% rename from modules/openapi-generator/src/test/java/org/openapitools/options/SlimFrameworkServerOptionsProvider.java rename to modules/openapi-generator/src/test/java/org/openapitools/options/PhpSlimServerOptionsProvider.java index 22f3a231f4b..d2aa3302d78 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/options/SlimFrameworkServerOptionsProvider.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/options/PhpSlimServerOptionsProvider.java @@ -6,7 +6,7 @@ import com.google.common.collect.ImmutableMap; import java.util.Map; -public class SlimFrameworkServerOptionsProvider implements OptionsProvider { +public class PhpSlimServerOptionsProvider implements OptionsProvider { public static final String SORT_PARAMS_VALUE = "false"; public static final String ENSURE_UNIQUE_PARAMS_VALUE = "true"; public static final String ALLOW_UNICODE_IDENTIFIERS_VALUE = "false"; From 659f8248f0dcd7f0a9081523413afef0f9d624f4 Mon Sep 17 00:00:00 2001 From: wing328 Date: Thu, 29 Mar 2018 11:41:53 +0800 Subject: [PATCH 22/29] add php symfony generator --- .../languages/PhpSymfonyServerCodegen.java | 593 ++++++++++++++++++ .../org.openapitools.codegen.CodegenConfig | 1 + 2 files changed, 594 insertions(+) create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PhpSymfonyServerCodegen.java diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PhpSymfonyServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PhpSymfonyServerCodegen.java new file mode 100644 index 00000000000..45bcdaf74f7 --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PhpSymfonyServerCodegen.java @@ -0,0 +1,593 @@ +package org.openapitools.codegen.languages; + +import org.openapitools.codegen.*; +import io.swagger.v3.oas.models.media.*; +import io.swagger.v3.oas.models.PathItem; +import io.swagger.v3.oas.models.PathItem.HttpMethod; +import io.swagger.v3.oas.models.*; +import io.swagger.v3.oas.models.parameters.*; +import io.swagger.v3.core.util.Yaml; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.util.*; + +public class PhpSymfonyServerCodegen extends AbstractPhpCodegen implements CodegenConfig { + @SuppressWarnings("hiding") + static Logger LOGGER = LoggerFactory.getLogger(PhpSymfonyServerCodegen.class); + + public static final String BUNDLE_NAME = "bundleName"; + public static final String COMPOSER_VENDOR_NAME = "composerVendorName"; + public static final String COMPOSER_PROJECT_NAME = "composerProjectName"; + public static final String PHP_LEGACY_SUPPORT = "phpLegacySupport"; + public static final Map SYMFONY_EXCEPTIONS; + protected String testsPackage; + protected String apiTestsPackage; + protected String modelTestsPackage; + protected String composerVendorName = "swagger"; + protected String composerProjectName = "server-bundle"; + protected String testsDirName = "Tests"; + protected String bundleName; + protected String bundleClassName; + protected String bundleExtensionName; + protected String bundleAlias; + protected String controllerDirName = "Controller"; + protected String serviceDirName = "Service"; + protected String controllerPackage; + protected String servicePackage; + protected Boolean phpLegacySupport = Boolean.TRUE; + + protected HashSet typeHintable; + + static { + SYMFONY_EXCEPTIONS = new HashMap<>(); + SYMFONY_EXCEPTIONS.put("400", "Symfony\\Component\\HttpKernel\\Exception\\BadRequestHttpException"); + SYMFONY_EXCEPTIONS.put("401", "Symfony\\Component\\HttpKernel\\Exception\\UnauthorizedHttpException"); + SYMFONY_EXCEPTIONS.put("403", "Symfony\\Component\\HttpKernel\\Exception\\AccessDeniedHttpException"); + SYMFONY_EXCEPTIONS.put("404", "Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException"); + SYMFONY_EXCEPTIONS.put("405", "Symfony\\Component\\HttpKernel\\Exception\\MethodNotAllowedHttpException"); + SYMFONY_EXCEPTIONS.put("406", "Symfony\\Component\\HttpKernel\\Exception\\NotAcceptableHttpException"); + SYMFONY_EXCEPTIONS.put("409", "Symfony\\Component\\HttpKernel\\Exception\\ConflictHttpException"); + SYMFONY_EXCEPTIONS.put("410", "Symfony\\Component\\HttpKernel\\Exception\\GoneHttpException"); + SYMFONY_EXCEPTIONS.put("411", "Symfony\\Component\\HttpKernel\\Exception\\LengthRequiredHttpException"); + SYMFONY_EXCEPTIONS.put("412", "Symfony\\Component\\HttpKernel\\Exception\\PreconditionFailedHttpException"); + SYMFONY_EXCEPTIONS.put("415", "Symfony\\Component\\HttpKernel\\Exception\\UnsupportedMediaTypeHttpException"); + SYMFONY_EXCEPTIONS.put("422", "Symfony\\Component\\HttpKernel\\Exception\\UnprocessableEntityHttpException"); + SYMFONY_EXCEPTIONS.put("428", "Symfony\\Component\\HttpKernel\\Exception\\PreconditionRequiredHttpException"); + SYMFONY_EXCEPTIONS.put("429", "Symfony\\Component\\HttpKernel\\Exception\\TooManyRequestsHttpException"); + SYMFONY_EXCEPTIONS.put("503", "Symfony\\Component\\HttpKernel\\Exception\\ServiceUnavailableHttpException"); + } + + public PhpSymfonyServerCodegen() { + super(); + + // clear import mapping (from default generator) as php does not use it + // at the moment + importMapping.clear(); + + supportsInheritance = true; + srcBasePath = "."; + setInvokerPackage("Swagger\\Server"); + setBundleName("SwaggerServer"); + packagePath = "SymfonyBundle-php"; + modelDirName = "Model"; + docsBasePath = "Resources" + File.separator + "docs"; + apiDocPath = docsBasePath + File.separator + apiDirName; + modelDocPath = docsBasePath + File.separator + modelDirName; + outputFolder = "generated-code" + File.separator + "php"; + apiTemplateFiles.put("api_controller.mustache", ".php"); + modelTestTemplateFiles.put("testing/model_test.mustache", ".php"); + apiTestTemplateFiles = new HashMap(); + apiTestTemplateFiles.put("testing/api_test.mustache", ".php"); + embeddedTemplateDir = templateDir = "php-symfony"; + + setReservedWordsLowerCase( + Arrays.asList( + // local variables used in api methods (endpoints) + "resourcePath", "httpBody", "queryParams", "headerParams", + "formParams", "_header_accept", "_tempBody", + + // PHP reserved words + "__halt_compiler", "abstract", "and", "array", "as", "break", "callable", "case", "catch", "class", "clone", "const", "continue", "declare", "default", "die", "do", "echo", "else", "elseif", "empty", "enddeclare", "endfor", "endforeach", "endif", "endswitch", "endwhile", "eval", "exit", "extends", "final", "for", "foreach", "function", "global", "goto", "if", "implements", "include", "include_once", "instanceof", "insteadof", "interface", "isset", "list", "namespace", "new", "or", "print", "private", "protected", "public", "require", "require_once", "return", "static", "switch", "throw", "trait", "try", "unset", "use", "var", "while", "xor" + ) + ); + + // ref: http://php.net/manual/en/language.types.intro.php + languageSpecificPrimitives = new HashSet( + Arrays.asList( + "bool", + "int", + "double", + "float", + "string", + "object", + "mixed", + "number", + "void", + "byte", + "array" + ) + ); + + defaultIncludes = new HashSet( + Arrays.asList( + "\\DateTime", + "UploadedFile" + ) + ); + + variableNamingConvention = "camelCase"; + + // provide primitives to mustache template + List sortedLanguageSpecificPrimitives = new ArrayList(languageSpecificPrimitives); + Collections.sort(sortedLanguageSpecificPrimitives); + String primitives = "'" + StringUtils.join(sortedLanguageSpecificPrimitives, "', '") + "'"; + additionalProperties.put("primitives", primitives); + + // ref: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types + typeMapping = new HashMap(); + typeMapping.put("integer", "int"); + typeMapping.put("long", "int"); + typeMapping.put("number", "float"); + typeMapping.put("float", "float"); + typeMapping.put("double", "double"); + typeMapping.put("string", "string"); + typeMapping.put("byte", "int"); + typeMapping.put("boolean", "bool"); + typeMapping.put("Date", "\\DateTime"); + typeMapping.put("DateTime", "\\DateTime"); + typeMapping.put("file", "UploadedFile"); + typeMapping.put("map", "array"); + typeMapping.put("array", "array"); + typeMapping.put("list", "array"); + typeMapping.put("object", "array"); + typeMapping.put("binary", "string"); + typeMapping.put("ByteArray", "string"); + typeMapping.put("UUID", "string"); + + cliOptions.add(new CliOption(COMPOSER_VENDOR_NAME, "The vendor name used in the composer package name. The template uses {{composerVendorName}}/{{composerProjectName}} for the composer package name. e.g. yaypets. IMPORTANT NOTE (2016/03): composerVendorName will be deprecated and replaced by gitUserId in the next swagger-codegen release")); + cliOptions.add(new CliOption(BUNDLE_NAME, "The name of the Symfony bundle. The template uses {{bundleName}}")); + cliOptions.add(new CliOption(COMPOSER_PROJECT_NAME, "The project name used in the composer package name. The template uses {{composerVendorName}}/{{composerProjectName}} for the composer package name. e.g. petstore-client. IMPORTANT NOTE (2016/03): composerProjectName will be deprecated and replaced by gitRepoId in the next swagger-codegen release")); + cliOptions.add(new CliOption(CodegenConstants.HIDE_GENERATION_TIMESTAMP, "hides the timestamp when files were generated") + .defaultValue(Boolean.TRUE.toString())); + cliOptions.add(new CliOption(PHP_LEGACY_SUPPORT, "Should the generated code be compatible with PHP 5.x?").defaultValue(Boolean.TRUE.toString())); + } + + public String getBundleName() { + return bundleName; + } + + public void setBundleName(String bundleName) { + this.bundleName = bundleName; + this.bundleClassName = bundleName + "Bundle"; + this.bundleExtensionName = bundleName + "Extension"; + this.bundleAlias = snakeCase(bundleName).replaceAll("([A-Z]+)", "\\_$1").toLowerCase(); + } + + public void setPhpLegacySupport(Boolean support) { + this.phpLegacySupport = support; + } + + public String controllerFileFolder() { + return (outputFolder + File.separator + toPackagePath(controllerPackage, srcBasePath)); + } + + @Override + public String escapeText(String input) { + if (input != null) { + // Trim the string to avoid leading and trailing spaces. + return super.escapeText(input).trim(); + } + return input; + } + + @Override + public CodegenType getTag() { + return CodegenType.SERVER; + } + + @Override + public String getName() { + return "php-symfony"; + } + + @Override + public String getHelp() { + return "Generates a Symfony server bundle."; + } + + @Override + public String apiFilename(String templateName, String tag) { + String suffix = apiTemplateFiles().get(templateName); + if (templateName.equals("api_controller.mustache")) + return controllerFileFolder() + '/' + toControllerName(tag) + suffix; + + return apiFileFolder() + '/' + toApiFilename(tag) + suffix; + } + + @Override + public void processOpts() { + super.processOpts(); + + // default HIDE_GENERATION_TIMESTAMP to true + if (!additionalProperties.containsKey(CodegenConstants.HIDE_GENERATION_TIMESTAMP)) { + additionalProperties.put(CodegenConstants.HIDE_GENERATION_TIMESTAMP, Boolean.TRUE.toString()); + } else { + additionalProperties.put(CodegenConstants.HIDE_GENERATION_TIMESTAMP, + Boolean.valueOf(additionalProperties().get(CodegenConstants.HIDE_GENERATION_TIMESTAMP).toString())); + } + + if (additionalProperties.containsKey(BUNDLE_NAME)) { + this.setBundleName((String) additionalProperties.get(BUNDLE_NAME)); + } else { + additionalProperties.put(BUNDLE_NAME, bundleName); + } + + if (additionalProperties.containsKey(COMPOSER_PROJECT_NAME)) { + this.setComposerProjectName((String) additionalProperties.get(COMPOSER_PROJECT_NAME)); + } else { + additionalProperties.put(COMPOSER_PROJECT_NAME, composerProjectName); + } + + if (additionalProperties.containsKey(COMPOSER_VENDOR_NAME)) { + this.setComposerVendorName((String) additionalProperties.get(COMPOSER_VENDOR_NAME)); + } else { + additionalProperties.put(COMPOSER_VENDOR_NAME, composerVendorName); + } + + if (additionalProperties.containsKey(PHP_LEGACY_SUPPORT)) { + this.setPhpLegacySupport(Boolean.valueOf((String) additionalProperties.get(PHP_LEGACY_SUPPORT))); + } else { + additionalProperties.put(PHP_LEGACY_SUPPORT, phpLegacySupport); + } + + additionalProperties.put("escapedInvokerPackage", invokerPackage.replace("\\", "\\\\")); + additionalProperties.put("controllerPackage", controllerPackage); + additionalProperties.put("servicePackage", servicePackage); + additionalProperties.put("apiTestsPackage", apiTestsPackage); + additionalProperties.put("modelTestsPackage", modelTestsPackage); + + // make Symonfy-specific properties available + additionalProperties.put("bundleName", bundleName); + additionalProperties.put("bundleClassName", bundleClassName); + additionalProperties.put("bundleExtensionName", bundleExtensionName); + additionalProperties.put("bundleAlias", bundleAlias); + + // make api and model src path available in mustache template + additionalProperties.put("apiSrcPath", "." + File.separator + toSrcPath(apiPackage, srcBasePath)); + additionalProperties.put("modelSrcPath", "." + File.separator + toSrcPath(modelPackage, srcBasePath)); + additionalProperties.put("testsSrcPath", "." + File.separator + toSrcPath(testsPackage, srcBasePath)); + additionalProperties.put("apiTestsSrcPath", "." + File.separator + toSrcPath(apiTestsPackage, srcBasePath)); + additionalProperties.put("modelTestsSrcPath", "." + File.separator + toSrcPath(modelTestsPackage, srcBasePath)); + additionalProperties.put("apiTestPath", "." + File.separator + testsDirName + File.separator + apiDirName); + additionalProperties.put("modelTestPath", "." + File.separator + testsDirName + File.separator + modelDirName); + + // make api and model doc path available in mustache template + additionalProperties.put("apiDocPath", apiDocPath); + additionalProperties.put("modelDocPath", modelDocPath); + + // make test path available in mustache template + additionalProperties.put("testsDirName", testsDirName); + + final String configDir = getPackagePath() + File.separator + "Resources" + File.separator + "config"; + final String dependencyInjectionDir = getPackagePath() + File.separator + "DependencyInjection"; + + supportingFiles.add(new SupportingFile("Controller.mustache", toPackagePath(controllerPackage, srcBasePath), "Controller.php")); + supportingFiles.add(new SupportingFile("Bundle.mustache", getPackagePath(), bundleClassName + ".php")); + supportingFiles.add(new SupportingFile("Extension.mustache", dependencyInjectionDir, bundleExtensionName + ".php")); + supportingFiles.add(new SupportingFile("ApiPass.mustache", dependencyInjectionDir + File.separator + "Compiler", bundleName + "ApiPass.php")); + supportingFiles.add(new SupportingFile("ApiServer.mustache", toPackagePath(apiPackage, srcBasePath), "ApiServer.php")); + + // Serialization components + supportingFiles.add(new SupportingFile("serialization/SerializerInterface.mustache", toPackagePath(servicePackage, srcBasePath), "SerializerInterface.php")); + supportingFiles.add(new SupportingFile("serialization/JmsSerializer.mustache", toPackagePath(servicePackage, srcBasePath), "JmsSerializer.php")); + supportingFiles.add(new SupportingFile("serialization/StrictJsonDeserializationVisitor.mustache", toPackagePath(servicePackage, srcBasePath), "StrictJsonDeserializationVisitor.php")); + supportingFiles.add(new SupportingFile("serialization/TypeMismatchException.mustache", toPackagePath(servicePackage, srcBasePath), "TypeMismatchException.php")); + // Validation components + supportingFiles.add(new SupportingFile("validation/ValidatorInterface.mustache", toPackagePath(servicePackage, srcBasePath), "ValidatorInterface.php")); + supportingFiles.add(new SupportingFile("validation/SymfonyValidator.mustache", toPackagePath(servicePackage, srcBasePath), "SymfonyValidator.php")); + + // Testing components + supportingFiles.add(new SupportingFile("testing/phpunit.xml.mustache", getPackagePath(), "phpunit.xml.dist")); + supportingFiles.add(new SupportingFile("testing/pom.xml", getPackagePath(), "pom.xml")); + supportingFiles.add(new SupportingFile("testing/AppKernel.php", toPackagePath(testsPackage, srcBasePath), "AppKernel.php")); + supportingFiles.add(new SupportingFile("testing/test_config.yml", toPackagePath(testsPackage, srcBasePath), "test_config.yml")); + + supportingFiles.add(new SupportingFile("routing.mustache", configDir, "routing.yml")); + supportingFiles.add(new SupportingFile("services.mustache", configDir, "services.yml")); + supportingFiles.add(new SupportingFile("composer.mustache", getPackagePath(), "composer.json")); + supportingFiles.add(new SupportingFile("autoload.mustache", getPackagePath(), "autoload.php")); + supportingFiles.add(new SupportingFile("README.mustache", getPackagePath(), "README.md")); + + supportingFiles.add(new SupportingFile(".travis.yml", getPackagePath(), ".travis.yml")); + supportingFiles.add(new SupportingFile(".php_cs", getPackagePath(), ".php_cs")); + supportingFiles.add(new SupportingFile("git_push.sh.mustache", getPackagePath(), "git_push.sh")); + + // Type-hintable primitive types + // ref: http://php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration + if (phpLegacySupport) { + typeHintable = new HashSet( + Arrays.asList( + "array" + ) + ); + } else { + typeHintable = new HashSet( + Arrays.asList( + "array", + "bool", + "float", + "int", + "string" + ) + ); + } + } + + @Override + public Map postProcessOperations(Map objs) { + objs = super.postProcessOperations(objs); + + Map operations = (Map) objs.get("operations"); + operations.put("controllerName", toControllerName((String) operations.get("pathPrefix"))); + operations.put("symfonyService", toSymfonyService((String) operations.get("pathPrefix"))); + + HashSet authMethods = new HashSet<>(); + List operationList = (List) operations.get("operation"); + + for (CodegenOperation op : operationList) { + // Loop through all input parameters to determine, whether we have to import something to + // make the input type available. + for (CodegenParameter param : op.allParams) { + // Determine if the parameter type is supported as a type hint and make it available + // to the templating engine + String typeHint = getTypeHint(param.dataType); + if (!typeHint.isEmpty()) { + param.vendorExtensions.put("x-parameterType", typeHint); + } + + if (param.isContainer) { + param.vendorExtensions.put("x-parameterType", getTypeHint(param.dataType + "[]")); + } + + // Create a variable to display the correct data type in comments for interfaces + param.vendorExtensions.put("x-commentType", param.dataType); + if (param.isContainer) { + param.vendorExtensions.put("x-commentType", param.dataType + "[]"); + } + + // Quote default values for strings + // @todo: The default values for headers, forms and query params are handled + // in DefaultCodegen fromParameter with no real possibility to override + // the functionality. Thus we are handling quoting of string values here + if (param.dataType.equals("string") && param.defaultValue != null && !param.defaultValue.isEmpty()) { + param.defaultValue = "'" + param.defaultValue + "'"; + } + } + + // Create a variable to display the correct return type in comments for interfaces + if (op.returnType != null) { + op.vendorExtensions.put("x-commentType", op.returnType); + if (!op.returnTypeIsPrimitive) { + op.vendorExtensions.put("x-commentType", op.returnType + "[]"); + } + } else { + op.vendorExtensions.put("x-commentType", "void"); + } + + // Add operation's authentication methods to whole interface + if (op.authMethods != null) { + authMethods.addAll(op.authMethods); + } + } + + operations.put("authMethods", authMethods); + + return objs; + } + + @Override + public Map postProcessModels(Map objs) { + objs = super.postProcessModels(objs); + + ArrayList modelsArray = (ArrayList) objs.get("models"); + Map models = (Map) modelsArray.get(0); + CodegenModel model = (CodegenModel) models.get("model"); + + // Simplify model var type + for (CodegenProperty var : model.vars) { + if (var.datatype != null) { + // Determine if the parameter type is supported as a type hint and make it available + // to the templating engine + String typeHint = getTypeHint(var.datatype); + if (!typeHint.isEmpty()) { + var.vendorExtensions.put("x-parameterType", typeHint); + } + + // Create a variable to display the correct data type in comments for models + var.vendorExtensions.put("x-commentType", var.datatype); + if (var.isContainer) { + var.vendorExtensions.put("x-commentType", var.datatype + "[]"); + } + + if (var.isBoolean) { + var.getter = var.getter.replaceAll("^get", "is"); + } + } + } + + return objs; + } + + @Override + public String escapeReservedWord(String name) { + if (this.reservedWordsMappings().containsKey(name)) { + return this.reservedWordsMappings().get(name); + } + return "_" + name; + } + + @Override + public String apiTestFileFolder() { + return (outputFolder + File.separator + toPackagePath(apiTestsPackage, srcBasePath)); + } + + @Override + public String modelTestFileFolder() { + return (outputFolder + File.separator + toPackagePath(modelTestsPackage, srcBasePath)); + } + + public void setComposerVendorName(String composerVendorName) { + this.composerVendorName = composerVendorName; + } + + public void setComposerProjectName(String composerProjectName) { + this.composerProjectName = composerProjectName; + } + + @Override + public void setInvokerPackage(String invokerPackage) { + super.setInvokerPackage(invokerPackage); + apiPackage = invokerPackage + "\\" + apiDirName; + modelPackage = invokerPackage + "\\" + modelDirName; + testsPackage = invokerPackage + "\\" + testsDirName; + apiTestsPackage = testsPackage + "\\" + apiDirName; + modelTestsPackage = testsPackage + "\\" + modelDirName; + controllerPackage = invokerPackage + "\\" + controllerDirName; + servicePackage = invokerPackage + "\\" + serviceDirName; + } + + @Override + public String getTypeDeclaration(Schema p) { + if (p instanceof ArraySchema) { + ArraySchema ap = (ArraySchema) p; + Schema inner = ap.getItems(); + return getTypeDeclaration(inner); + } + + if (p instanceof MapSchema) { + MapSchema mp = (MapSchema) p; + Schema inner = (Schema) mp.getAdditionalProperties(); + return getTypeDeclaration(inner); + } + + if (!StringUtils.isEmpty(p.get$ref())) { + return getTypeDeclaration(getPropertyTypeDeclaration(p)); + } + + return getPropertyTypeDeclaration(p); + } + + /** + * Output the type declaration of the property + * + * @param p Swagger Schema object + * @return a string presentation of the property type + */ + public String getPropertyTypeDeclaration(Schema p) { + String openAPIType = getSchemaType(p); + if (typeMapping.containsKey(openAPIType)) { + return typeMapping.get(openAPIType); + } + return openAPIType; + } + + @Override + public String getTypeDeclaration(String name) { + if (!languageSpecificPrimitives.contains(name)) { + return modelPackage + "\\" + name; + } + return super.getTypeDeclaration(name); + } + + /** + * Return the fully-qualified "Model" name for import + * + * @param name the name of the "Model" + * @return the fully-qualified "Model" name for import + */ + @Override + public String toModelImport(String name) { + if ("".equals(modelPackage())) { + return name; + } else { + return modelPackage() + "\\" + name; + } + } + + @Override + public String toEnumValue(String value, String datatype) { + if ("int".equals(datatype) || "double".equals(datatype) || "float".equals(datatype)) { + return value; + } else { + return "\"" + escapeText(value) + "\""; + } + } + + /** + * Return the regular expression/JSON schema pattern (http://json-schema.org/latest/json-schema-validation.html#anchor33) + * + * @param pattern the pattern (regular expression) + * @return properly-escaped pattern + */ + @Override + public String toRegularExpression(String pattern) { + return escapeText(pattern); + } + + public String toApiName(String name) { + if (name.isEmpty()) { + return "DefaultApiInterface"; + } + return camelize(name, false) + "ApiInterface"; + } + + protected String toControllerName(String name) { + if (name.isEmpty()) { + return "DefaultController"; + } + return camelize(name, false) + "Controller"; + } + + protected String toSymfonyService(String name) { + String prefix = composerVendorName + ".api."; + if (name.isEmpty()) { + return prefix + "default"; + } + + return prefix + name; + } + + protected String getTypeHint(String type) { + // Type hint array types + if (type.endsWith("[]")) { + return "array"; + } + + // Check if the type is a native type that is type hintable in PHP + if (typeHintable.contains(type)) { + return type; + } + + // Default includes are referenced by their fully-qualified class name (including namespace) + if (defaultIncludes.contains(type)) { + return type; + } + + // Model classes are assumed to be imported and we reference them by their class name + if (isModelClass(type)) { + // This parameter is an instance of a model + return extractSimpleName(type); + } + + // PHP does not support type hinting for this parameter data type + return ""; + } + + protected Boolean isModelClass(String type) { + return Boolean.valueOf(type.contains(modelPackage())); + } +} 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 757146defcb..6eade1cd694 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 @@ -15,6 +15,7 @@ org.openapitools.codegen.languages.LuaClientCodegen org.openapitools.codegen.languages.PhpLumenServerCodegen org.openapitools.codegen.languages.PhpSlimServerCodegen org.openapitools.codegen.languages.PhpSilexServerCodegen +org.openapitools.codegen.languages.PhpSymfonyServerCodegen org.openapitools.codegen.languages.PhpZendExpressivePathHandlerServerCodegen org.openapitools.codegen.languages.ObjcClientCodegen org.openapitools.codegen.languages.PerlClientCodegen From e9630f1608f19c1734e529b3398c5ba9883c156c Mon Sep 17 00:00:00 2001 From: wing328 Date: Thu, 29 Mar 2018 14:51:23 +0800 Subject: [PATCH 23/29] add ts generators --- .../TypeScriptAngularJsClientCodegen.java | 101 +++++++++ .../TypeScriptAureliaClientCodegen.java | 140 +++++++++++++ .../TypeScriptFetchClientCodegen.java | 147 +++++++++++++ .../TypeScriptJqueryClientCodegen.java | 195 ++++++++++++++++++ .../org.openapitools.codegen.CodegenConfig | 4 + 5 files changed, 587 insertions(+) create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAngularJsClientCodegen.java create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAureliaClientCodegen.java create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptJqueryClientCodegen.java diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAngularJsClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAngularJsClientCodegen.java new file mode 100644 index 00000000000..a2c464d12bc --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAngularJsClientCodegen.java @@ -0,0 +1,101 @@ +package org.openapitools.codegen.languages; + +import java.io.File; + +import io.swagger.v3.parser.util.SchemaTypeUtil; +import org.apache.commons.lang3.StringUtils; +import org.openapitools.codegen.CliOption; +import org.openapitools.codegen.CodegenModel; +import org.openapitools.codegen.CodegenParameter; +import org.openapitools.codegen.CodegenOperation; +import org.openapitools.codegen.SupportingFile; +import org.openapitools.codegen.utils.SemVer; + +import io.swagger.v3.oas.models.media.*; +import io.swagger.v3.oas.models.PathItem; +import io.swagger.v3.oas.models.PathItem.HttpMethod; +import io.swagger.v3.oas.models.*; +import io.swagger.v3.oas.models.parameters.*; +import io.swagger.v3.oas.models.info.*; + +public class TypeScriptAngularJsClientCodegen extends AbstractTypeScriptClientCodegen { + + public TypeScriptAngularJsClientCodegen() { + super(); + outputFolder = "generated-code/typescript-angularjs"; + modelTemplateFiles.put("model.mustache", ".ts"); + apiTemplateFiles.put("api.mustache", ".ts"); + embeddedTemplateDir = templateDir = "typescript-angularjs"; + apiPackage = "api"; + modelPackage = "model"; + } + + @Override + public String getName() { + return "typescript-angularjs"; + } + + @Override + public String getHelp() { + return "Generates a TypeScript AngularJS client library."; + } + + @Override + public void processOpts() { + super.processOpts(); + supportingFiles.add(new SupportingFile("models.mustache", modelPackage().replace('.', File.separatorChar), "models.ts")); + supportingFiles.add(new SupportingFile("apis.mustache", apiPackage().replace('.', File.separatorChar), "api.ts")); + supportingFiles.add(new SupportingFile("index.mustache", getIndexDirectory(), "index.ts")); + supportingFiles.add(new SupportingFile("api.module.mustache", getIndexDirectory(), "api.module.ts")); + supportingFiles.add(new SupportingFile("git_push.sh.mustache", "", "git_push.sh")); + supportingFiles.add(new SupportingFile("gitignore", "", ".gitignore")); + + } + + @Override + public String getSchemaType(Schema p) { + String openAPIType = super.getSchemaType(p); + if (isLanguagePrimitive(openAPIType) || isLanguageGenericType(openAPIType)) { + return openAPIType; + } + return addModelPrefix(openAPIType); + } + + @Override + public void postProcessParameter(CodegenParameter parameter) { + super.postProcessParameter(parameter); + parameter.dataType = addModelPrefix(parameter.dataType); + } + + private String getIndexDirectory() { + String indexPackage = modelPackage.substring(0, Math.max(0, modelPackage.lastIndexOf('.'))); + return indexPackage.replace('.', File.separatorChar); + } + + private String addModelPrefix(String openAPIType) { + String type = null; + if (typeMapping.containsKey(openAPIType)) { + type = typeMapping.get(openAPIType); + } else { + type = openAPIType; + } + + if (!isLanguagePrimitive(type) && !isLanguageGenericType(type)) { + type = "models." + openAPIType; + } + return type; + } + + private boolean isLanguagePrimitive(String type) { + return languageSpecificPrimitives.contains(type); + } + + private boolean isLanguageGenericType(String type) { + for (String genericType : languageGenericTypes) { + if (type.startsWith(genericType + "<")) { + return true; + } + } + return false; + } +} diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAureliaClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAureliaClientCodegen.java new file mode 100644 index 00000000000..c71af8bc98f --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAureliaClientCodegen.java @@ -0,0 +1,140 @@ +package org.openapitools.codegen.languages; + +import io.swagger.v3.parser.util.SchemaTypeUtil; +import org.apache.commons.lang3.StringUtils; +import org.openapitools.codegen.*; + +import io.swagger.v3.oas.models.media.*; +import io.swagger.v3.oas.models.PathItem; +import io.swagger.v3.oas.models.PathItem.HttpMethod; +import io.swagger.v3.oas.models.*; +import io.swagger.v3.oas.models.parameters.*; +import io.swagger.v3.oas.models.info.*; + +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.TreeSet; + +public class TypeScriptAureliaClientCodegen extends AbstractTypeScriptClientCodegen { + + public static final String NPM_NAME = "npmName"; + public static final String NPM_VERSION = "npmVersion"; + + protected String npmName = null; + protected String npmVersion = "1.0.0"; + + public TypeScriptAureliaClientCodegen() { + super(); + + apiTemplateFiles.put("api.mustache", ".ts"); + + // clear import mapping (from default generator) as TS does not use it + // at the moment + importMapping.clear(); + + outputFolder = "generated-code/typescript-aurelia"; + embeddedTemplateDir = templateDir = "typescript-aurelia"; + this.cliOptions.add(new CliOption(NPM_NAME, "The name under which you want to publish generated npm package")); + this.cliOptions.add(new CliOption(NPM_VERSION, "The version of your npm package")); + } + + @Override + public String getName() { + return "typescript-aurelia"; + } + + @Override + public String getHelp() { + return "Generates a TypeScript client library for the Aurelia framework (beta)."; + } + + public String getNpmName() { + return npmName; + } + + public void setNpmName(String npmName) { + this.npmName = npmName; + } + + public String getNpmVersion() { + return npmVersion; + } + + public void setNpmVersion(String npmVersion) { + this.npmVersion = npmVersion; + } + + @Override + public void processOpts() { + super.processOpts(); + + if (additionalProperties.containsKey(NPM_NAME)) { + this.setNpmName(additionalProperties.get(NPM_NAME).toString()); + } + + if (additionalProperties.containsKey(NPM_VERSION)) { + this.setNpmVersion(additionalProperties.get(NPM_VERSION).toString()); + } + + // Set supporting files + supportingFiles.add(new SupportingFile("models.mustache", "", "models.ts")); + supportingFiles.add(new SupportingFile("index.ts.mustache", "", "index.ts")); + supportingFiles.add(new SupportingFile("Api.ts.mustache", "", "Api.ts")); + supportingFiles.add(new SupportingFile("AuthStorage.ts.mustache", "", "AuthStorage.ts")); + supportingFiles.add(new SupportingFile("git_push.sh.mustache", "", "git_push.sh")); + supportingFiles.add(new SupportingFile("README.md", "", "README.md")); + supportingFiles.add(new SupportingFile("package.json.mustache", "", "package.json")); + supportingFiles.add(new SupportingFile("tsconfig.json.mustache", "", "tsconfig.json")); + supportingFiles.add(new SupportingFile("tslint.json.mustache", "", "tslint.json")); + supportingFiles.add(new SupportingFile("gitignore", "", ".gitignore")); + } + + @Override + public Map postProcessOperations(Map objs) { + objs = super.postProcessOperations(objs); + + HashSet modelImports = new HashSet<>(); + Map operations = (Map) objs.get("operations"); + List operationList = (List) operations.get("operation"); + for (CodegenOperation op : operationList) { + // Aurelia uses "asGet", "asPost", ... methods; change the method format + op.httpMethod = initialCaps(op.httpMethod.toLowerCase()); + + // Collect models to be imported + for (CodegenParameter param : op.allParams) { + if (!param.isPrimitiveType && !param.isListContainer && !param.dataType.equals("any")) { + modelImports.add(param.dataType); + } + } + if (op.returnBaseType != null && !op.returnTypeIsPrimitive) { + modelImports.add(op.returnBaseType); + } + } + + objs.put("modelImports", modelImports); + + return objs; + } + + @Override + public Map postProcessModels(Map objs) { + // process enum in models + List models = (List) postProcessModelsEnum(objs).get("models"); + for (Object _mo : models) { + Map mo = (Map) _mo; + CodegenModel cm = (CodegenModel) mo.get("model"); + cm.imports = new TreeSet(cm.imports); + for (CodegenProperty var : cm.vars) { + // name enum with model name, e.g. StatuEnum => PetStatusEnum + if (Boolean.TRUE.equals(var.isEnum)) { + var.datatypeWithEnum = var.datatypeWithEnum.replace(var.enumName, cm.classname + var.enumName); + var.enumName = cm.classname + var.enumName; + } + } + } + + return objs; + } + +} diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java new file mode 100644 index 00000000000..2e4eff4e388 --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java @@ -0,0 +1,147 @@ +package org.openapitools.codegen.languages; + +import io.swagger.v3.parser.util.SchemaTypeUtil; +import org.apache.commons.lang3.StringUtils; +import org.openapitools.codegen.*; + +import io.swagger.v3.oas.models.media.*; +import io.swagger.v3.oas.models.PathItem; +import io.swagger.v3.oas.models.PathItem.HttpMethod; +import io.swagger.v3.oas.models.*; +import io.swagger.v3.oas.models.parameters.*; +import io.swagger.v3.oas.models.info.*; + +import java.io.File; +import java.text.SimpleDateFormat; +import java.util.*; + +public class TypeScriptFetchClientCodegen extends AbstractTypeScriptClientCodegen { + private static final SimpleDateFormat SNAPSHOT_SUFFIX_FORMAT = new SimpleDateFormat("yyyyMMddHHmm"); + + public static final String NPM_NAME = "npmName"; + public static final String NPM_VERSION = "npmVersion"; + public static final String NPM_REPOSITORY = "npmRepository"; + public static final String SNAPSHOT = "snapshot"; + public static final String WITH_INTERFACES = "withInterfaces"; + + protected String npmName = null; + protected String npmVersion = "1.0.0"; + protected String npmRepository = null; + + public TypeScriptFetchClientCodegen() { + super(); + + // clear import mapping (from default generator) as TS does not use it + // at the moment + importMapping.clear(); + + outputFolder = "generated-code/typescript-fetch"; + embeddedTemplateDir = templateDir = "typescript-fetch"; + + this.cliOptions.add(new CliOption(NPM_NAME, "The name under which you want to publish generated npm package")); + this.cliOptions.add(new CliOption(NPM_VERSION, "The version of your npm package")); + this.cliOptions.add(new CliOption(NPM_REPOSITORY, "Use this property to set an url your private npmRepo in the package.json")); + this.cliOptions.add(new CliOption(SNAPSHOT, "When setting this property to true the version will be suffixed with -SNAPSHOT.yyyyMMddHHmm", SchemaTypeUtil.BOOLEAN_TYPE).defaultValue(Boolean.FALSE.toString())); + this.cliOptions.add(new CliOption(WITH_INTERFACES, "Setting this property to true will generate interfaces next to the default class implementations.", SchemaTypeUtil.BOOLEAN_TYPE).defaultValue(Boolean.FALSE.toString())); + } + + @Override + public String getName() { + return "typescript-fetch"; + } + + @Override + public String getHelp() { + return "Generates a TypeScript client library using Fetch API (beta)."; + } + + public String getNpmName() { + return npmName; + } + + public void setNpmName(String npmName) { + this.npmName = npmName; + } + + public String getNpmVersion() { + return npmVersion; + } + + public void setNpmVersion(String npmVersion) { + this.npmVersion = npmVersion; + } + + public String getNpmRepository() { + return npmRepository; + } + + public void setNpmRepository(String npmRepository) { + this.npmRepository = npmRepository; + } + + @Override + public void processOpts() { + super.processOpts(); + supportingFiles.add(new SupportingFile("index.mustache", "", "index.ts")); + supportingFiles.add(new SupportingFile("api.mustache", "", "api.ts")); + supportingFiles.add(new SupportingFile("configuration.mustache", "", "configuration.ts")); + supportingFiles.add(new SupportingFile("custom.d.mustache", "", "custom.d.ts")); + supportingFiles.add(new SupportingFile("git_push.sh.mustache", "", "git_push.sh")); + supportingFiles.add(new SupportingFile("gitignore", "", ".gitignore")); + + if (additionalProperties.containsKey(NPM_NAME)) { + addNpmPackageGeneration(); + } + } + + @Override + public String getTypeDeclaration(Schema p) { + Schema inner; + if (p instanceof ArraySchema) { + inner = ((ArraySchema) p).getItems(); + return this.getSchemaType(p) + "<" + this.getTypeDeclaration(inner) + ">"; + } else if (p instanceof MapSchema) { + inner = (Schema) p.getAdditionalProperties(); + return "{ [key: string]: " + this.getTypeDeclaration(inner) + "; }"; + } else if (p instanceof FileSchema) { + return "any"; + } else if (p instanceof BinarySchema) { + return "any"; + } else if (!StringUtils.isEmpty(p.get$ref())) { // model + return "any"; + } else { + return super.getTypeDeclaration(p); + } + } + + @Override + protected void addAdditionPropertiesToCodeGenModel(CodegenModel codegenModel, Schema schema) { + codegenModel.additionalPropertiesType = getTypeDeclaration((Schema) schema.getAdditionalProperties()); + addImport(codegenModel, codegenModel.additionalPropertiesType); + } + + private void addNpmPackageGeneration() { + if (additionalProperties.containsKey(NPM_NAME)) { + this.setNpmName(additionalProperties.get(NPM_NAME).toString()); + } + + if (additionalProperties.containsKey(NPM_VERSION)) { + this.setNpmVersion(additionalProperties.get(NPM_VERSION).toString()); + } + + if (additionalProperties.containsKey(SNAPSHOT) && Boolean.valueOf(additionalProperties.get(SNAPSHOT).toString())) { + this.setNpmVersion(npmVersion + "-SNAPSHOT." + SNAPSHOT_SUFFIX_FORMAT.format(new Date())); + } + additionalProperties.put(NPM_VERSION, npmVersion); + + if (additionalProperties.containsKey(NPM_REPOSITORY)) { + this.setNpmRepository(additionalProperties.get(NPM_REPOSITORY).toString()); + } + + //Files for building our lib + supportingFiles.add(new SupportingFile("README.mustache", "", "README.md")); + supportingFiles.add(new SupportingFile("package.mustache", "", "package.json")); + supportingFiles.add(new SupportingFile("tsconfig.mustache", "", "tsconfig.json")); + } + +} diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptJqueryClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptJqueryClientCodegen.java new file mode 100644 index 00000000000..117f6a3c1cf --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptJqueryClientCodegen.java @@ -0,0 +1,195 @@ +package org.openapitools.codegen.languages; + +import io.swagger.v3.parser.util.SchemaTypeUtil; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.openapitools.codegen.*; + +import io.swagger.v3.oas.models.media.*; +import io.swagger.v3.oas.models.PathItem; +import io.swagger.v3.oas.models.PathItem.HttpMethod; +import io.swagger.v3.oas.models.*; +import io.swagger.v3.oas.models.parameters.*; +import io.swagger.v3.oas.models.info.*; + +import java.io.File; +import java.text.SimpleDateFormat; +import java.util.Date; + +public class TypeScriptJqueryClientCodegen extends AbstractTypeScriptClientCodegen { + private static final Logger LOGGER = LoggerFactory.getLogger(TypeScriptJqueryClientCodegen.class); + private static final SimpleDateFormat SNAPSHOT_SUFFIX_FORMAT = new SimpleDateFormat("yyyyMMddHHmm"); + + public static final String NPM_NAME = "npmName"; + public static final String NPM_VERSION = "npmVersion"; + public static final String NPM_REPOSITORY = "npmRepository"; + public static final String SNAPSHOT = "snapshot"; + public static final String JQUERY_ALREADY_IMPORTED = "jqueryAlreadyImported"; + + protected String npmName = null; + protected String npmVersion = "1.0.0"; + protected String npmRepository = null; + + public TypeScriptJqueryClientCodegen() { + super(); + + modelTemplateFiles.put("model.mustache", ".ts"); + apiTemplateFiles.put("api.mustache", ".ts"); + typeMapping.put("Date", "Date"); + apiPackage = "api"; + modelPackage = "model"; + + outputFolder = "generated-code/typescript-jquery"; + embeddedTemplateDir = templateDir = "typescript-jquery"; + + this.cliOptions.add(new CliOption(NPM_NAME, "The name under which you want to publish generated npm package")); + this.cliOptions.add(new CliOption(NPM_VERSION, "The version of your npm package")); + this.cliOptions.add(new CliOption(NPM_REPOSITORY, "Use this property to set an url your private npmRepo in the package.json")); + this.cliOptions.add(new CliOption(SNAPSHOT, + "When setting this property to true the version will be suffixed with -SNAPSHOT.yyyyMMddHHmm", + SchemaTypeUtil.BOOLEAN_TYPE).defaultValue(Boolean.FALSE.toString())); + this.cliOptions.add(new CliOption(JQUERY_ALREADY_IMPORTED, + "When using this in legacy app using mix of typescript and javascript, this will only declare jquery and not import it", + SchemaTypeUtil.BOOLEAN_TYPE).defaultValue(Boolean.FALSE.toString())); + } + + @Override + public String getName() { + return "typescript-jquery"; + } + + @Override + public String getHelp() { + return "Generates a TypeScript jquery client library."; + } + + public void setNpmName(String npmName) { + this.npmName = npmName; + } + + public void setNpmVersion(String npmVersion) { + this.npmVersion = npmVersion; + } + + public String getNpmVersion() { + return npmVersion; + } + + public String getNpmRepository() { + return npmRepository; + } + + public void setNpmRepository(String npmRepository) { + this.npmRepository = npmRepository; + } + + @Override + public void processOpts() { + super.processOpts(); + + supportingFiles.add(new SupportingFile("git_push.sh.mustache", "", "git_push.sh")); + supportingFiles.add(new SupportingFile("models.mustache", modelPackage().replace('.', File.separatorChar), "models.ts")); + supportingFiles.add(new SupportingFile("apis.mustache", apiPackage().replace('.', File.separatorChar), "api.ts")); + supportingFiles.add(new SupportingFile("configuration.mustache", getIndexDirectory(), "configuration.ts")); + supportingFiles.add(new SupportingFile("index.mustache", getIndexDirectory(), "index.ts")); + supportingFiles.add(new SupportingFile("variables.mustache", getIndexDirectory(), "variables.ts")); + + //LOGGER.warn("check additionals: " + additionalProperties.get(NPM_NAME)); + if (additionalProperties.containsKey(NPM_NAME)) { + addNpmPackageGeneration(); + } + } + + @Override + public String getSchemaType(Schema p) { + String openAPIType = super.getSchemaType(p); + if (p instanceof StringSchema) { + if (p.getEnum() != null) { + return openAPIType; + } + } + if (isLanguagePrimitive(openAPIType) || isLanguageGenericType(openAPIType)) { + return openAPIType; + } + return addModelPrefix(openAPIType); + } + + @Override + public void postProcessParameter(CodegenParameter parameter) { + super.postProcessParameter(parameter); + + if (!parameter.isEnum) { + parameter.dataType = addModelPrefix(parameter.dataType); + } + } + + @Override + protected void addAdditionPropertiesToCodeGenModel(CodegenModel codegenModel, Schema schema) { + codegenModel.additionalPropertiesType = getSchemaType((Schema) schema.getAdditionalProperties()); + addImport(codegenModel, codegenModel.additionalPropertiesType); + } + + private String addModelPrefix(String openAPIType) { + String type = null; + if (typeMapping.containsKey(openAPIType)) { + type = typeMapping.get(openAPIType); + } else { + type = openAPIType; + } + + if (!isLanguagePrimitive(type) && !isLanguageGenericType(type)) { + type = "models." + openAPIType; + } + return type; + } + + private boolean isLanguagePrimitive(String type) { + return languageSpecificPrimitives.contains(type); + } + + private boolean isLanguageGenericType(String type) { + for (String genericType : languageGenericTypes) { + if (type.startsWith(genericType + "<")) { + return true; + } + } + return false; + } + + private String getPackageRootDirectory() { + String indexPackage = modelPackage.substring(0, Math.max(0, modelPackage.lastIndexOf('.'))); + return indexPackage.replace('.', File.separatorChar); + } + + private void addNpmPackageGeneration() { + if (additionalProperties.containsKey(NPM_NAME)) { + this.setNpmName(additionalProperties.get(NPM_NAME).toString()); + } + + if (additionalProperties.containsKey(NPM_VERSION)) { + this.setNpmVersion(additionalProperties.get(NPM_VERSION).toString()); + } + + if (additionalProperties.containsKey(SNAPSHOT) && Boolean.valueOf(additionalProperties.get(SNAPSHOT).toString())) { + this.setNpmVersion(npmVersion + "-SNAPSHOT." + SNAPSHOT_SUFFIX_FORMAT.format(new Date())); + } + additionalProperties.put(NPM_VERSION, npmVersion); + + if (additionalProperties.containsKey(NPM_REPOSITORY)) { + this.setNpmRepository(additionalProperties.get(NPM_REPOSITORY).toString()); + } + + //Files for building our lib + supportingFiles.add(new SupportingFile("README.mustache", getPackageRootDirectory(), "README.md")); + supportingFiles.add(new SupportingFile("package.mustache", getPackageRootDirectory(), "package.json")); + supportingFiles.add(new SupportingFile("typings.mustache", getPackageRootDirectory(), "typings.json")); + supportingFiles.add(new SupportingFile("tsconfig.mustache", getPackageRootDirectory(), "tsconfig.json")); + } + + private String getIndexDirectory() { + String indexPackage = modelPackage.substring(0, Math.max(0, modelPackage.lastIndexOf('.'))); + return indexPackage.replace('.', File.separatorChar); + } + +} 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 6eade1cd694..e7285806685 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 @@ -31,3 +31,7 @@ org.openapitools.codegen.languages.ScalaClientCodegen org.openapitools.codegen.languages.SinatraServerCodegen org.openapitools.codegen.languages.TizenClientCodegen org.openapitools.codegen.languages.TypeScriptAngularClientCodegen +org.openapitools.codegen.languages.TypeScriptAngularJsClientCodegen +org.openapitools.codegen.languages.TypeScriptAureliaClientCodegen +org.openapitools.codegen.languages.TypeScriptFetchClientCodegen +org.openapitools.codegen.languages.TypeScriptJqueryClientCodegen From e5073db2d4f88bdc86ced77448efc22f3b456085 Mon Sep 17 00:00:00 2001 From: wing328 Date: Thu, 29 Mar 2018 15:02:15 +0800 Subject: [PATCH 24/29] add ts node generator --- .../TypeScriptNodeClientCodegen.java | 137 ++++++++++++++++++ .../org.openapitools.codegen.CodegenConfig | 1 + 2 files changed, 138 insertions(+) create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptNodeClientCodegen.java diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptNodeClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptNodeClientCodegen.java new file mode 100644 index 00000000000..3cfac742edb --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptNodeClientCodegen.java @@ -0,0 +1,137 @@ +package org.openapitools.codegen.languages; + +import io.swagger.v3.parser.util.SchemaTypeUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.text.SimpleDateFormat; +import java.util.Date; + +import org.openapitools.codegen.*; + +import io.swagger.v3.oas.models.media.*; +import io.swagger.v3.oas.models.PathItem; +import io.swagger.v3.oas.models.PathItem.HttpMethod; +import io.swagger.v3.oas.models.*; +import io.swagger.v3.oas.models.parameters.*; +import io.swagger.v3.oas.models.info.*; + +public class TypeScriptNodeClientCodegen extends AbstractTypeScriptClientCodegen { + private static final Logger LOGGER = LoggerFactory.getLogger(TypeScriptNodeClientCodegen.class); + private static final SimpleDateFormat SNAPSHOT_SUFFIX_FORMAT = new SimpleDateFormat("yyyyMMddHHmm"); + + public static final String NPM_NAME = "npmName"; + public static final String NPM_VERSION = "npmVersion"; + public static final String NPM_REPOSITORY = "npmRepository"; + public static final String SNAPSHOT = "snapshot"; + + protected String npmName = null; + protected String npmVersion = "1.0.0"; + protected String npmRepository = null; + + public TypeScriptNodeClientCodegen() { + super(); + + typeMapping.put("file", "Buffer"); + + // clear import mapping (from default generator) as TS does not use it + // at the moment + importMapping.clear(); + + outputFolder = "generated-code/typescript-node"; + embeddedTemplateDir = templateDir = "typescript-node"; + + this.cliOptions.add(new CliOption(NPM_NAME, "The name under which you want to publish generated npm package")); + this.cliOptions.add(new CliOption(NPM_VERSION, "The version of your npm package")); + this.cliOptions.add(new CliOption(NPM_REPOSITORY, "Use this property to set an url your private npmRepo in the package.json")); + this.cliOptions.add(new CliOption(SNAPSHOT, + "When setting this property to true the version will be suffixed with -SNAPSHOT.yyyyMMddHHmm", + SchemaTypeUtil.BOOLEAN_TYPE).defaultValue(Boolean.FALSE.toString())); + } + + @Override + public String getName() { + return "typescript-node"; + } + + @Override + public String getHelp() { + return "Generates a TypeScript NodeJS client library."; + } + + @Override + public boolean isDataTypeFile(final String dataType) { + return "Buffer".equals(dataType); + } + + @Override + public String getTypeDeclaration(Schema p) { + if (p instanceof FileSchema) { + return "Buffer"; + } else if (p instanceof BinarySchema) { + return "Buffer"; + } + return super.getTypeDeclaration(p); + } + + public void setNpmName(String npmName) { + this.npmName = npmName; + } + + public void setNpmVersion(String npmVersion) { + this.npmVersion = npmVersion; + } + + public String getNpmVersion() { + return npmVersion; + } + + public String getNpmRepository() { + return npmRepository; + } + + public void setNpmRepository(String npmRepository) { + this.npmRepository = npmRepository; + } + + @Override + public void processOpts() { + super.processOpts(); + supportingFiles.add(new SupportingFile("api.mustache", null, "api.ts")); + supportingFiles.add(new SupportingFile("git_push.sh.mustache", "", "git_push.sh")); + supportingFiles.add(new SupportingFile("gitignore", "", ".gitignore")); + + if (additionalProperties.containsKey(NPM_NAME)) { + addNpmPackageGeneration(); + } + } + + private void addNpmPackageGeneration() { + if (additionalProperties.containsKey(NPM_NAME)) { + this.setNpmName(additionalProperties.get(NPM_NAME).toString()); + } + + if (additionalProperties.containsKey(NPM_VERSION)) { + this.setNpmVersion(additionalProperties.get(NPM_VERSION).toString()); + } + + if (additionalProperties.containsKey(SNAPSHOT) && Boolean.valueOf(additionalProperties.get(SNAPSHOT).toString())) { + this.setNpmVersion(npmVersion + "-SNAPSHOT." + SNAPSHOT_SUFFIX_FORMAT.format(new Date())); + } + additionalProperties.put(NPM_VERSION, npmVersion); + + if (additionalProperties.containsKey(NPM_REPOSITORY)) { + this.setNpmRepository(additionalProperties.get(NPM_REPOSITORY).toString()); + } + + //Files for building our lib + supportingFiles.add(new SupportingFile("package.mustache", getPackageRootDirectory(), "package.json")); + supportingFiles.add(new SupportingFile("tsconfig.mustache", getPackageRootDirectory(), "tsconfig.json")); + } + + private String getPackageRootDirectory() { + String indexPackage = modelPackage.substring(0, Math.max(0, modelPackage.lastIndexOf('.'))); + return indexPackage.replace('.', File.separatorChar); + } +} 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 e7285806685..a3ee838eea5 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 @@ -35,3 +35,4 @@ org.openapitools.codegen.languages.TypeScriptAngularJsClientCodegen org.openapitools.codegen.languages.TypeScriptAureliaClientCodegen org.openapitools.codegen.languages.TypeScriptFetchClientCodegen org.openapitools.codegen.languages.TypeScriptJqueryClientCodegen +org.openapitools.codegen.languages.TypeScriptNodeClientCodegen From 5646120b88ca7487f3ccbdece9a2924a6a0459a0 Mon Sep 17 00:00:00 2001 From: wing328 Date: Thu, 29 Mar 2018 15:06:49 +0800 Subject: [PATCH 25/29] minor update to android generator --- .../languages/AndroidClientCodegen.java | 167 +++++++++--------- 1 file changed, 83 insertions(+), 84 deletions(-) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AndroidClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AndroidClientCodegen.java index 0d5d50ed3ab..36b1458b833 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AndroidClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AndroidClientCodegen.java @@ -182,17 +182,17 @@ public class AndroidClientCodegen extends DefaultCodegen implements CodegenConfi @Override public String getSchemaType(Schema p) { - String swaggerType = super.getSchemaType(p); + String openAPIType = super.getSchemaType(p); String type = null; - if (typeMapping.containsKey(swaggerType)) { - type = typeMapping.get(swaggerType); + if (typeMapping.containsKey(openAPIType)) { + type = typeMapping.get(openAPIType); if (languageSpecificPrimitives.contains(type) || type.indexOf(".") >= 0 || type.equals("Map") || type.equals("List") || type.equals("File") || type.equals("Date")) { return type; } } else { - type = swaggerType; + type = openAPIType; } return toModelName(type); } @@ -435,86 +435,6 @@ public class AndroidClientCodegen extends DefaultCodegen implements CodegenConfi } else { throw new IllegalArgumentException("Invalid 'library' option specified: '" + getLibrary() + "'. Must be 'httpclient' or 'volley' (default)"); } - - } - - private void addSupportingFilesForHttpClient() { - // documentation files - modelDocTemplateFiles.put("model_doc.mustache", ".md"); - apiDocTemplateFiles.put("api_doc.mustache", ".md"); - supportingFiles.add(new SupportingFile("README.mustache", "", "README.md")); - - supportingFiles.add(new SupportingFile("pom.mustache", "", "pom.xml")); - supportingFiles.add(new SupportingFile("settings.gradle.mustache", "", "settings.gradle")); - supportingFiles.add(new SupportingFile("build.mustache", "", "build.gradle")); - supportingFiles.add(new SupportingFile("manifest.mustache", projectFolder, "AndroidManifest.xml")); - supportingFiles.add(new SupportingFile("apiInvoker.mustache", - (sourceFolder + File.separator + invokerPackage).replace(".", File.separator), "ApiInvoker.java")); - supportingFiles.add(new SupportingFile("httpPatch.mustache", - (sourceFolder + File.separator + invokerPackage).replace(".", File.separator), "HttpPatch.java")); - supportingFiles.add(new SupportingFile("jsonUtil.mustache", - (sourceFolder + File.separator + invokerPackage).replace(".", File.separator), "JsonUtil.java")); - supportingFiles.add(new SupportingFile("apiException.mustache", - (sourceFolder + File.separator + invokerPackage).replace(".", File.separator), "ApiException.java")); - supportingFiles.add(new SupportingFile("Pair.mustache", - (sourceFolder + File.separator + invokerPackage).replace(".", File.separator), "Pair.java")); - supportingFiles.add(new SupportingFile("git_push.sh.mustache", "", "git_push.sh")); - supportingFiles.add(new SupportingFile("gitignore.mustache", "", ".gitignore")); - - // gradle wrapper 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")); - - } - - private void addSupportingFilesForVolley() { - // documentation files - modelDocTemplateFiles.put("model_doc.mustache", ".md"); - apiDocTemplateFiles.put("api_doc.mustache", ".md"); - supportingFiles.add(new SupportingFile("README.mustache", "", "README.md")); - - supportingFiles.add(new SupportingFile("git_push.sh.mustache", "", "git_push.sh")); - supportingFiles.add(new SupportingFile("gitignore.mustache", "", ".gitignore")); - supportingFiles.add(new SupportingFile("pom.mustache", "", "pom.xml")); - // supportingFiles.add(new SupportingFile("settings.gradle.mustache", "", "settings.gradle")); - supportingFiles.add(new SupportingFile("build.mustache", "", "build.gradle")); - supportingFiles.add(new SupportingFile("manifest.mustache", projectFolder, "AndroidManifest.xml")); - supportingFiles.add(new SupportingFile("apiInvoker.mustache", - (sourceFolder + File.separator + invokerPackage).replace(".", File.separator), "ApiInvoker.java")); - supportingFiles.add(new SupportingFile("jsonUtil.mustache", - (sourceFolder + File.separator + invokerPackage).replace(".", File.separator), "JsonUtil.java")); - supportingFiles.add(new SupportingFile("apiException.mustache", - (sourceFolder + File.separator + invokerPackage).replace(".", File.separator), "ApiException.java")); - supportingFiles.add(new SupportingFile("Pair.mustache", - (sourceFolder + File.separator + invokerPackage).replace(".", File.separator), "Pair.java")); - supportingFiles.add(new SupportingFile("request/getrequest.mustache", - (sourceFolder + File.separator + requestPackage).replace(".", File.separator), "GetRequest.java")); - supportingFiles.add(new SupportingFile("request/postrequest.mustache", - (sourceFolder + File.separator + requestPackage).replace(".", File.separator), "PostRequest.java")); - supportingFiles.add(new SupportingFile("request/putrequest.mustache", - (sourceFolder + File.separator + requestPackage).replace(".", File.separator), "PutRequest.java")); - supportingFiles.add(new SupportingFile("request/deleterequest.mustache", - (sourceFolder + File.separator + requestPackage).replace(".", File.separator), "DeleteRequest.java")); - supportingFiles.add(new SupportingFile("request/patchrequest.mustache", - (sourceFolder + File.separator + requestPackage).replace(".", File.separator), "PatchRequest.java")); - supportingFiles.add(new SupportingFile("auth/apikeyauth.mustache", - (sourceFolder + File.separator + authPackage).replace(".", File.separator), "ApiKeyAuth.java")); - supportingFiles.add(new SupportingFile("auth/httpbasicauth.mustache", - (sourceFolder + File.separator + authPackage).replace(".", File.separator), "HttpBasicAuth.java")); - supportingFiles.add(new SupportingFile("auth/authentication.mustache", - (sourceFolder + File.separator + authPackage).replace(".", File.separator), "Authentication.java")); - - // gradle wrapper 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")); } public Boolean getUseAndroidMavenGradlePlugin() { @@ -592,4 +512,83 @@ public class AndroidClientCodegen extends DefaultCodegen implements CodegenConfi return input.replace("*/", "*_/").replace("/*", "/_*"); } + private void addSupportingFilesForVolley() { + // documentation files + modelDocTemplateFiles.put("model_doc.mustache", ".md"); + apiDocTemplateFiles.put("api_doc.mustache", ".md"); + supportingFiles.add(new SupportingFile("README.mustache", "", "README.md")); + + supportingFiles.add(new SupportingFile("git_push.sh.mustache", "", "git_push.sh")); + supportingFiles.add(new SupportingFile("gitignore.mustache", "", ".gitignore")); + supportingFiles.add(new SupportingFile("pom.mustache", "", "pom.xml")); + // supportingFiles.add(new SupportingFile("settings.gradle.mustache", "", "settings.gradle")); + supportingFiles.add(new SupportingFile("build.mustache", "", "build.gradle")); + supportingFiles.add(new SupportingFile("manifest.mustache", projectFolder, "AndroidManifest.xml")); + supportingFiles.add(new SupportingFile("apiInvoker.mustache", + (sourceFolder + File.separator + invokerPackage).replace(".", File.separator), "ApiInvoker.java")); + supportingFiles.add(new SupportingFile("jsonUtil.mustache", + (sourceFolder + File.separator + invokerPackage).replace(".", File.separator), "JsonUtil.java")); + supportingFiles.add(new SupportingFile("apiException.mustache", + (sourceFolder + File.separator + invokerPackage).replace(".", File.separator), "ApiException.java")); + supportingFiles.add(new SupportingFile("Pair.mustache", + (sourceFolder + File.separator + invokerPackage).replace(".", File.separator), "Pair.java")); + supportingFiles.add(new SupportingFile("request/getrequest.mustache", + (sourceFolder + File.separator + requestPackage).replace(".", File.separator), "GetRequest.java")); + supportingFiles.add(new SupportingFile("request/postrequest.mustache", + (sourceFolder + File.separator + requestPackage).replace(".", File.separator), "PostRequest.java")); + supportingFiles.add(new SupportingFile("request/putrequest.mustache", + (sourceFolder + File.separator + requestPackage).replace(".", File.separator), "PutRequest.java")); + supportingFiles.add(new SupportingFile("request/deleterequest.mustache", + (sourceFolder + File.separator + requestPackage).replace(".", File.separator), "DeleteRequest.java")); + supportingFiles.add(new SupportingFile("request/patchrequest.mustache", + (sourceFolder + File.separator + requestPackage).replace(".", File.separator), "PatchRequest.java")); + supportingFiles.add(new SupportingFile("auth/apikeyauth.mustache", + (sourceFolder + File.separator + authPackage).replace(".", File.separator), "ApiKeyAuth.java")); + supportingFiles.add(new SupportingFile("auth/httpbasicauth.mustache", + (sourceFolder + File.separator + authPackage).replace(".", File.separator), "HttpBasicAuth.java")); + supportingFiles.add(new SupportingFile("auth/authentication.mustache", + (sourceFolder + File.separator + authPackage).replace(".", File.separator), "Authentication.java")); + + // gradle wrapper 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")); + } + + private void addSupportingFilesForHttpClient() { + // documentation files + modelDocTemplateFiles.put("model_doc.mustache", ".md"); + apiDocTemplateFiles.put("api_doc.mustache", ".md"); + supportingFiles.add(new SupportingFile("README.mustache", "", "README.md")); + + supportingFiles.add(new SupportingFile("pom.mustache", "", "pom.xml")); + supportingFiles.add(new SupportingFile("settings.gradle.mustache", "", "settings.gradle")); + supportingFiles.add(new SupportingFile("build.mustache", "", "build.gradle")); + supportingFiles.add(new SupportingFile("manifest.mustache", projectFolder, "AndroidManifest.xml")); + supportingFiles.add(new SupportingFile("apiInvoker.mustache", + (sourceFolder + File.separator + invokerPackage).replace(".", File.separator), "ApiInvoker.java")); + supportingFiles.add(new SupportingFile("httpPatch.mustache", + (sourceFolder + File.separator + invokerPackage).replace(".", File.separator), "HttpPatch.java")); + supportingFiles.add(new SupportingFile("jsonUtil.mustache", + (sourceFolder + File.separator + invokerPackage).replace(".", File.separator), "JsonUtil.java")); + supportingFiles.add(new SupportingFile("apiException.mustache", + (sourceFolder + File.separator + invokerPackage).replace(".", File.separator), "ApiException.java")); + supportingFiles.add(new SupportingFile("Pair.mustache", + (sourceFolder + File.separator + invokerPackage).replace(".", File.separator), "Pair.java")); + supportingFiles.add(new SupportingFile("git_push.sh.mustache", "", "git_push.sh")); + supportingFiles.add(new SupportingFile("gitignore.mustache", "", ".gitignore")); + + // gradle wrapper 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")); + + } + } From f8d089fb5446340756284e5da2cd680de4c09f3e Mon Sep 17 00:00:00 2001 From: wing328 Date: Thu, 29 Mar 2018 16:01:15 +0800 Subject: [PATCH 26/29] add haskell client generator --- .../languages/HaskellHttpClientCodegen.java | 1322 +++++++++++++++++ .../org.openapitools.codegen.CodegenConfig | 1 + 2 files changed, 1323 insertions(+) create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/HaskellHttpClientCodegen.java diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/HaskellHttpClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/HaskellHttpClientCodegen.java new file mode 100644 index 00000000000..4729cb23c6a --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/HaskellHttpClientCodegen.java @@ -0,0 +1,1322 @@ +package org.openapitools.codegen.languages; + +import java.util.*; +import java.util.regex.Pattern; +import java.io.File; + +import io.swagger.v3.oas.models.security.SecurityScheme; +import org.openapitools.codegen.*; +import io.swagger.v3.oas.models.*; +import io.swagger.v3.oas.models.media.*; +import io.swagger.v3.oas.models.parameters.*; +import io.swagger.v3.core.util.Yaml; + +import com.fasterxml.jackson.core.JsonProcessingException; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.StringEscapeUtils; +import org.apache.commons.lang3.tuple.Pair; + +import java.util.regex.Matcher; + +public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenConfig { + + // source folder where to write the files + protected String sourceFolder = "lib"; + + protected String defaultDateFormat = "%Y-%m-%d"; + protected String defaultCabalVersion = "0.1.0.0"; + protected String modulePath = null; + + protected Boolean useMonadLogger = false; + protected Boolean allowNonUniqueOperationIds = false; + protected Boolean genEnums = true; + + // CLI PROPS + public static final String PROP_ALLOW_FROMJSON_NULLS = "allowFromJsonNulls"; + public static final String PROP_ALLOW_NONUNIQUE_OPERATION_IDS = "allowNonUniqueOperationIds"; + public static final String PROP_ALLOW_TOJSON_NULLS = "allowToJsonNulls"; + public static final String PROP_BASE_MODULE = "baseModule"; + public static final String PROP_CABAL_PACKAGE = "cabalPackage"; + public static final String PROP_CABAL_VERSION = "cabalVersion"; + public static final String PROP_CONFIG_TYPE = "configType"; + public static final String PROP_DATETIME_FORMAT = "dateTimeFormat"; + public static final String PROP_DATE_FORMAT = "dateFormat"; + public static final String PROP_GENERATE_ENUMS = "generateEnums"; + public static final String PROP_GENERATE_FORM_URLENCODED_INSTANCES = "generateFormUrlEncodedInstances"; + public static final String PROP_GENERATE_LENSES = "generateLenses"; + public static final String PROP_GENERATE_MODEL_CONSTRUCTORS = "generateModelConstructors"; + public static final String PROP_INLINE_MIME_TYPES = "inlineMimeTypes"; + public static final String PROP_MODEL_DERIVING = "modelDeriving"; + public static final String PROP_REQUEST_TYPE = "requestType"; + public static final String PROP_STRICT_FIELDS = "strictFields"; + public static final String PROP_USE_MONAD_LOGGER = "useMonadLogger"; + + // protected String MODEL_IMPORTS = "modelImports"; + // protected String MODEL_EXTENSIONS = "modelExtensions"; + + private static final Pattern LEADING_UNDERSCORE = Pattern.compile("^_+"); + + static final String MEDIA_TYPE = "mediaType"; + static final String MIME_NO_CONTENT = "MimeNoContent"; + static final String MIME_ANY = "MimeAny"; + + // vendor extensions + static final String X_ALL_UNIQUE_PARAMS = "x-allUniqueParams"; + static final String X_COLLECTION_FORMAT = "x-collectionFormat"; + static final String X_HADDOCK_PATH = "x-haddockPath"; + static final String X_HAS_BODY_OR_FORM_PARAM = "x-hasBodyOrFormParam"; + static final String X_HAS_ENUM_SECTION = "x-hasEnumSection"; + static final String X_HAS_MIME_FORM_URL_ENCODED = "x-hasMimeFormUrlEncoded"; + static final String X_HAS_NEW_TAG = "x-hasNewTag"; + static final String X_HAS_OPTIONAL_PARAMS = "x-hasOptionalParams"; + static final String X_HAS_UNKNOWN_MIME_TYPES = "x-hasUnknownMimeTypes"; + static final String X_HAS_UNKNOWN_RETURN = "x-hasUnknownReturn"; + static final String X_INLINE_CONTENT_TYPE = "x-inlineContentType"; + static final String X_INLINE_ACCEPT = "x-inlineAccept"; + static final String X_IS_BODY_OR_FORM_PARAM = "x-isBodyOrFormParam"; + static final String X_IS_BODY_PARAM = "x-isBodyParam"; + static final String X_MEDIA_DATA_TYPE = "x-mediaDataType"; + static final String X_DATA_TYPE = "x-dataType"; + static final String X_ENUM_VALUES = "x-enumValues"; + static final String X_MEDIA_IS_JSON = "x-mediaIsJson"; + static final String X_MEDIA_IS_WILDCARD = "x-mediaIsWildcard"; + static final String X_MIME_TYPES = "x-mimeTypes"; + static final String X_OPERATION_TYPE = "x-operationType"; + static final String X_PARAM_NAME_TYPE = "x-paramNameType"; + static final String X_PATH = "x-path"; + static final String X_RETURN_TYPE = "x-returnType"; + static final String X_STRICT_FIELDS = "x-strictFields"; + static final String X_UNKNOWN_MIME_TYPES = "x-unknownMimeTypes"; + static final String X_USE_MONAD_LOGGER = "x-useMonadLogger"; + static final String X_ALLOW_NONUNIQUE_OPERATION_IDS = "x-allowNonUniqueOperationIds"; + static final String X_NEWTYPE = "x-newtype"; + static final String X_ENUM = "x-enum"; + + protected ArrayList> unknownMimeTypes = new ArrayList<>(); + protected Map> uniqueParamNameTypes = new HashMap<>(); + protected Map> modelMimeTypes = new HashMap<>(); + protected Map knownMimeDataTypes = new HashMap<>(); + protected Set typeNames = new HashSet(); + protected Set modelTypeNames = new HashSet(); + + final private static Pattern JSON_MIME_PATTERN = Pattern.compile("(?i)application/.*json(;.*)?"); + + public CodegenType getTag() { + return CodegenType.CLIENT; + } + + public String getName() { + return "haskell-http-client"; + } + + public String getHelp() { + return "Generates a Haskell http-client library."; + } + + public HaskellHttpClientCodegen() { + super(); + + // override the mapping to keep the original mapping in Haskell + specialCharReplacements.put("-", "Dash"); + specialCharReplacements.put(">", "GreaterThan"); + specialCharReplacements.put("<", "LessThan"); + + // backslash and double quote need double the escapement for both Java and Haskell + specialCharReplacements.remove("\\"); + specialCharReplacements.remove("\""); + specialCharReplacements.put("\\\\", "Back_Slash"); + specialCharReplacements.put("\\\"", "Double_Quote"); + + // set the output folder here + outputFolder = "generated-code/haskell-http-client"; + + embeddedTemplateDir = templateDir = "haskell-http-client"; + apiPackage = "API"; + //modelPackage = "Model"; + + // Haskell keywords and reserved function names, taken mostly from https://wiki.haskell.org/Keywords + setReservedWordsLowerCase( + Arrays.asList( + // Keywords + "as", "case", "of", + "class", "data", "family", + "default", "deriving", + "do", "forall", "foreign", "hiding", + "if", "then", "else", + "import", "infix", "infixl", "infixr", + "instance", "let", "in", + "mdo", "module", "newtype", + "proc", "qualified", "rec", + "type", "where", "pure", "return", + "Accept", "ContentType" + ) + ); + + supportingFiles.add(new SupportingFile("README.mustache", "", "README.md")); + supportingFiles.add(new SupportingFile("stack.mustache", "", "stack.yaml")); + supportingFiles.add(new SupportingFile("Setup.mustache", "", "Setup.hs")); + supportingFiles.add(new SupportingFile("gitignore.mustache", "", ".gitignore")); + supportingFiles.add(new SupportingFile(".travis.yml", "", ".travis.yml")); + supportingFiles.add(new SupportingFile("git_push.sh.mustache", "", "git_push.sh")); + + supportingFiles.add(new SupportingFile("tests/ApproxEq.mustache", "tests", "ApproxEq.hs")); + supportingFiles.add(new SupportingFile("tests/Instances.mustache", "tests", "Instances.hs")); + supportingFiles.add(new SupportingFile("tests/PropMime.mustache", "tests", "PropMime.hs")); + supportingFiles.add(new SupportingFile("tests/Test.mustache", "tests", "Test.hs")); + + languageSpecificPrimitives = new HashSet( + Arrays.asList( + "Bool", + "String", + "Int", + "Integer", + "Float", + "Char", + "Double", + "List", + "FilePath", + "Text" + ) + ); + + typeMapping.clear(); + // prim + typeMapping.put("boolean", "Bool"); + typeMapping.put("int", "Int"); + typeMapping.put("long", "Integer"); + typeMapping.put("short", "Int"); + typeMapping.put("char", "Char"); + typeMapping.put("float", "Float"); + typeMapping.put("double", "Double"); + typeMapping.put("number", "Double"); + typeMapping.put("integer", "Int"); + typeMapping.put("file", "FilePath"); + // lib + typeMapping.put("string", "Text"); + typeMapping.put("UUID", "Text"); + typeMapping.put("any", "A.Value"); + typeMapping.put("set", "Set.Set"); + // newtype + typeMapping.put("binary", "Binary"); + typeMapping.put("ByteArray", "ByteArray"); + typeMapping.put("date", "Date"); + typeMapping.put("DateTime", "DateTime"); + + knownMimeDataTypes.put("application/json", "MimeJSON"); + knownMimeDataTypes.put("application/xml", "MimeXML"); + knownMimeDataTypes.put("application/x-www-form-urlencoded", "MimeFormUrlEncoded"); + knownMimeDataTypes.put("application/octet-stream", "MimeOctetStream"); + knownMimeDataTypes.put("multipart/form-data", "MimeMultipartFormData"); + knownMimeDataTypes.put("text/plain", "MimePlainText"); + knownMimeDataTypes.put("*/*", MIME_ANY); + + importMapping.clear(); + + //cliOptions.add(CliOption.newString(CodegenConstants.MODEL_PACKAGE, CodegenConstants.MODEL_PACKAGE_DESC)); + //cliOptions.add(CliOption.newString(CodegenConstants.API_PACKAGE, CodegenConstants.API_PACKAGE_DESC)); + + cliOptions.add(CliOption.newString(PROP_CABAL_PACKAGE, "Set the cabal package name, which consists of one or more alphanumeric words separated by hyphens")); + cliOptions.add(CliOption.newString(PROP_CABAL_VERSION, "Set the cabal version number, consisting of a sequence of one or more integers separated by dots")); + cliOptions.add(CliOption.newString(PROP_BASE_MODULE, "Set the base module namespace")); + cliOptions.add(CliOption.newString(PROP_REQUEST_TYPE, "Set the name of the type used to generate requests")); + cliOptions.add(CliOption.newString(PROP_CONFIG_TYPE, "Set the name of the type used for configuration")); + + cliOptions.add(CliOption.newBoolean(PROP_ALLOW_FROMJSON_NULLS, "allow JSON Null during model decoding from JSON").defaultValue(Boolean.TRUE.toString())); + cliOptions.add(CliOption.newBoolean(PROP_ALLOW_TOJSON_NULLS, "allow emitting JSON Null during model encoding to JSON").defaultValue(Boolean.FALSE.toString())); + cliOptions.add(CliOption.newBoolean(PROP_ALLOW_NONUNIQUE_OPERATION_IDS, "allow different API modules to contain the same operationId. Each API must be imported qualified").defaultValue(Boolean.FALSE.toString())); + cliOptions.add(CliOption.newBoolean(PROP_GENERATE_LENSES, "Generate Lens optics for Models").defaultValue(Boolean.TRUE.toString())); + cliOptions.add(CliOption.newBoolean(PROP_GENERATE_MODEL_CONSTRUCTORS, "Generate smart constructors (only supply required fields) for models").defaultValue(Boolean.TRUE.toString())); + cliOptions.add(CliOption.newBoolean(PROP_GENERATE_ENUMS, "Generate specific datatypes for swagger enums").defaultValue(Boolean.TRUE.toString())); + cliOptions.add(CliOption.newBoolean(PROP_GENERATE_FORM_URLENCODED_INSTANCES, "Generate FromForm/ToForm instances for models that are used by operations that produce or consume application/x-www-form-urlencoded").defaultValue(Boolean.TRUE.toString())); + cliOptions.add(CliOption.newBoolean(PROP_INLINE_MIME_TYPES, "Inline (hardcode) the content-type and accept parameters on operations, when there is only 1 option").defaultValue(Boolean.TRUE.toString())); + + + cliOptions.add(CliOption.newString(PROP_MODEL_DERIVING, "Additional classes to include in the deriving() clause of Models")); + cliOptions.add(CliOption.newBoolean(PROP_STRICT_FIELDS, "Add strictness annotations to all model fields").defaultValue((Boolean.TRUE.toString()))); + cliOptions.add(CliOption.newBoolean(PROP_USE_MONAD_LOGGER, "Use the monad-logger package to provide logging (if false, use the katip logging package)").defaultValue((Boolean.FALSE.toString()))); + + cliOptions.add(CliOption.newString(PROP_DATETIME_FORMAT, "format string used to parse/render a datetime")); + cliOptions.add(CliOption.newString(PROP_DATE_FORMAT, "format string used to parse/render a date").defaultValue(defaultDateFormat)); + + cliOptions.add(CliOption.newBoolean(CodegenConstants.HIDE_GENERATION_TIMESTAMP, "hides the timestamp when files were generated").defaultValue(Boolean.TRUE.toString())); + + } + + public void setAllowNonUniqueOperationIds(Boolean value) { + additionalProperties.put(X_ALLOW_NONUNIQUE_OPERATION_IDS, value); + this.allowNonUniqueOperationIds = value; + } + + public void setAllowFromJsonNulls(Boolean value) { + additionalProperties.put(PROP_ALLOW_FROMJSON_NULLS, value); + } + + public void setAllowToJsonNulls(Boolean value) { + additionalProperties.put(PROP_ALLOW_TOJSON_NULLS, value); + } + + public void setGenerateModelConstructors(Boolean value) { + additionalProperties.put(PROP_GENERATE_MODEL_CONSTRUCTORS, value); + } + + public void setGenerateEnums(Boolean value) { + additionalProperties.put(PROP_GENERATE_ENUMS, value); + genEnums = value; + } + + public void setGenerateFormUrlEncodedInstances(Boolean value) { + additionalProperties.put(PROP_GENERATE_FORM_URLENCODED_INSTANCES, value); + } + + public void setInlineMimeTypes(Boolean value) { + additionalProperties.put(PROP_INLINE_MIME_TYPES, value); + } + + public void setGenerateLenses(Boolean value) { + additionalProperties.put(PROP_GENERATE_LENSES, value); + } + + public void setModelDeriving(String value) { + if (StringUtils.isBlank(value)) { + additionalProperties.remove(PROP_MODEL_DERIVING); + } else { + additionalProperties.put(PROP_MODEL_DERIVING, StringUtils.join(value.split(" "), ",")); + } + } + + public void setDateTimeFormat(String value) { + setStringProp(PROP_DATETIME_FORMAT, value); + } + + public void setDateFormat(String value) { + setStringProp(PROP_DATE_FORMAT, value); + } + + public void setCabalPackage(String value) { + setStringProp(PROP_CABAL_PACKAGE, value); + } + + public void setCabalVersion(String value) { + setStringProp(PROP_CABAL_VERSION, value); + } + + public void setBaseModule(String value) { + setStringProp(PROP_BASE_MODULE, value); + } + + public void setRequestType(String value) { + setStringProp(PROP_REQUEST_TYPE, value); + } + + public void setConfigType(String value) { + setStringProp(PROP_CONFIG_TYPE, value); + } + + public void setStrictFields(Boolean value) { + additionalProperties.put(X_STRICT_FIELDS, value); + } + + public void setUseMonadLogger(Boolean value) { + additionalProperties.put(X_USE_MONAD_LOGGER, value); + this.useMonadLogger = value; + } + + private void setStringProp(String key, String value) { + if (StringUtils.isBlank(value)) { + additionalProperties.remove(key); + } else { + additionalProperties.put(key, value); + } + } + + private String getStringProp(String key) { + return (String) additionalProperties.get(key); + } + + @Override + public void processOpts() { + super.processOpts(); + // default HIDE_GENERATION_TIMESTAMP to true + if (additionalProperties.containsKey(CodegenConstants.HIDE_GENERATION_TIMESTAMP)) { + convertPropertyToBooleanAndWriteBack(CodegenConstants.HIDE_GENERATION_TIMESTAMP); + } else { + additionalProperties.put(CodegenConstants.HIDE_GENERATION_TIMESTAMP, true); + } + + if (additionalProperties.containsKey(PROP_ALLOW_FROMJSON_NULLS)) { + setAllowFromJsonNulls(convertPropertyToBoolean(PROP_ALLOW_FROMJSON_NULLS)); + } else { + setAllowFromJsonNulls(true); + } + + if (additionalProperties.containsKey(PROP_ALLOW_TOJSON_NULLS)) { + setAllowToJsonNulls(convertPropertyToBoolean(PROP_ALLOW_TOJSON_NULLS)); + } else { + setAllowToJsonNulls(false); + } + + if (additionalProperties.containsKey(PROP_ALLOW_NONUNIQUE_OPERATION_IDS)) { + setAllowNonUniqueOperationIds(convertPropertyToBoolean(PROP_ALLOW_NONUNIQUE_OPERATION_IDS)); + } else { + setAllowNonUniqueOperationIds(false); + } + + if (additionalProperties.containsKey(PROP_GENERATE_MODEL_CONSTRUCTORS)) { + setGenerateModelConstructors(convertPropertyToBoolean(PROP_GENERATE_MODEL_CONSTRUCTORS)); + } else { + setGenerateModelConstructors(true); + } + + if (additionalProperties.containsKey(PROP_GENERATE_ENUMS)) { + setGenerateEnums(convertPropertyToBoolean(PROP_GENERATE_ENUMS)); + } else { + setGenerateEnums(true); + } + + if (additionalProperties.containsKey(PROP_GENERATE_FORM_URLENCODED_INSTANCES)) { + setGenerateFormUrlEncodedInstances(convertPropertyToBoolean(PROP_GENERATE_FORM_URLENCODED_INSTANCES)); + } else { + setGenerateFormUrlEncodedInstances(true); + } + + if (additionalProperties.containsKey(PROP_INLINE_MIME_TYPES)) { + setInlineMimeTypes(convertPropertyToBoolean(PROP_INLINE_MIME_TYPES)); + } else { + setInlineMimeTypes(true); + } + + if (additionalProperties.containsKey(PROP_GENERATE_LENSES)) { + setGenerateLenses(convertPropertyToBoolean(PROP_GENERATE_LENSES)); + } else { + setGenerateLenses(true); + } + + if (additionalProperties.containsKey(PROP_MODEL_DERIVING)) { + setModelDeriving(additionalProperties.get(PROP_MODEL_DERIVING).toString()); + } else { + setModelDeriving(""); + } + + if (additionalProperties.containsKey(PROP_DATETIME_FORMAT)) { + setDateTimeFormat(additionalProperties.get(PROP_DATETIME_FORMAT).toString()); + } else { + setDateTimeFormat(null); // default should be null + } + + if (additionalProperties.containsKey(PROP_DATE_FORMAT)) { + setDateFormat(additionalProperties.get(PROP_DATE_FORMAT).toString()); + } else { + setDateFormat(defaultDateFormat); + } + + if (additionalProperties.containsKey(PROP_STRICT_FIELDS)) { + setStrictFields(convertPropertyToBoolean(PROP_STRICT_FIELDS)); + } else { + setStrictFields(true); + } + if (additionalProperties.containsKey(PROP_USE_MONAD_LOGGER)) { + setUseMonadLogger(convertPropertyToBoolean(PROP_USE_MONAD_LOGGER)); + } else { + setUseMonadLogger(false); + } + + if (additionalProperties.containsKey(PROP_CABAL_PACKAGE)) { + setCabalPackage(additionalProperties.get(PROP_CABAL_PACKAGE).toString()); + } + if (additionalProperties.containsKey(PROP_CABAL_VERSION)) { + setCabalVersion(additionalProperties.get(PROP_CABAL_VERSION).toString()); + } else { + setCabalVersion(defaultCabalVersion); + } + if (additionalProperties.containsKey(PROP_BASE_MODULE)) { + setBaseModule(additionalProperties.get(PROP_BASE_MODULE).toString()); + } + if (additionalProperties.containsKey(PROP_REQUEST_TYPE)) { + setRequestType(additionalProperties.get(PROP_REQUEST_TYPE).toString()); + } + if (additionalProperties.containsKey(PROP_CONFIG_TYPE)) { + setConfigType(additionalProperties.get(PROP_CONFIG_TYPE).toString()); + } + } + + @Override + public void preprocessOpenAPI(OpenAPI openAPI) { + String baseTitle = openAPI.getInfo().getTitle(); + + if (baseTitle == null) { + baseTitle = "OpenAPI"; + } else { + baseTitle = baseTitle.trim(); + // Drop any API suffix + if (baseTitle.toUpperCase().endsWith("API")) { + baseTitle = baseTitle.substring(0, baseTitle.length() - 3); + } + } + + if (!additionalProperties.containsKey(PROP_CABAL_PACKAGE)) { + List words = new ArrayList<>(); + for (String word : baseTitle.split(" ")) { + words.add(word.toLowerCase()); + } + setCabalPackage(StringUtils.join(words, "-")); + } + + if (!additionalProperties.containsKey(PROP_BASE_MODULE)) { + List wordsCaps = new ArrayList(); + for (String word : baseTitle.split(" ")) { + wordsCaps.add(firstLetterToUpper(word)); + } + setBaseModule(StringUtils.join(wordsCaps, "")); + } + + modulePath = sourceFolder + File.separator + getStringProp(PROP_BASE_MODULE).replace('.', File.separatorChar); + + String topLevelPath = StringUtils.substringBeforeLast(modulePath, String.valueOf(File.separatorChar)); + String lastPath = StringUtils.substringAfterLast(modulePath, String.valueOf(File.separatorChar)); + + if (!additionalProperties.containsKey(PROP_REQUEST_TYPE)) { + setRequestType(lastPath + "Request"); + } + + if (!additionalProperties.containsKey(PROP_CONFIG_TYPE)) { + setConfigType(lastPath + "Config"); + } + + // root + supportingFiles.add(new SupportingFile("haskell-http-client.cabal.mustache", "", getStringProp(PROP_CABAL_PACKAGE) + ".cabal")); + supportingFiles.add(new SupportingFile("swagger.mustache", "", "swagger.yaml")); + + // lib + supportingFiles.add(new SupportingFile("TopLevel.mustache", topLevelPath, lastPath + ".hs")); + supportingFiles.add(new SupportingFile("Client.mustache", modulePath, "Client.hs")); + + + if (!allowNonUniqueOperationIds) { + supportingFiles.add(new SupportingFile("APIS.mustache", modulePath, "API.hs")); + } + supportingFiles.add(new SupportingFile("Core.mustache", modulePath, "Core.hs")); + supportingFiles.add(new SupportingFile("Model.mustache", modulePath, "Model.hs")); + supportingFiles.add(new SupportingFile("MimeTypes.mustache", modulePath, "MimeTypes.hs")); + + // logger + supportingFiles.add(new SupportingFile(useMonadLogger ? "LoggingMonadLogger.mustache" : "LoggingKatip.mustache", modulePath, "Logging.hs")); + + apiTemplateFiles.put("API.mustache", ".hs"); + // modelTemplateFiles.put("Model.mustache", ".hs"); + + // lens + if ((boolean) additionalProperties.get(PROP_GENERATE_LENSES)) { + supportingFiles.add(new SupportingFile("ModelLens.mustache", modulePath, "ModelLens.hs")); + } + + additionalProperties.put("cabalName", getStringProp(PROP_CABAL_PACKAGE)); + additionalProperties.put("pathsName", getStringProp(PROP_CABAL_PACKAGE).replace('-', '_')); + additionalProperties.put("requestType", getStringProp(PROP_REQUEST_TYPE)); + additionalProperties.put("configType", getStringProp(PROP_CONFIG_TYPE)); + additionalProperties.put("openApiVersion", openAPI.getOpenapi()); + + super.preprocessOpenAPI(openAPI); + } + + @Override + public Map postProcessSupportingFileData(Map objs) { + OpenAPI openAPI = (OpenAPI) objs.get("openapi"); + if (openAPI != null) { + try { + objs.put("openapi-yaml", Yaml.mapper().writeValueAsString(openAPI)); + } catch (JsonProcessingException e) { + LOGGER.error(e.getMessage(), e); + } + } + return super.postProcessSupportingFileData(objs); + } + + @Override + public String getTypeDeclaration(Schema p) { + if (p instanceof ArraySchema) { + ArraySchema ap = (ArraySchema) p; + Schema inner = ap.getItems(); + return "[" + getTypeDeclaration(inner) + "]"; + } else if (p instanceof MapSchema) { + MapSchema mp = (MapSchema) p; + Schema inner = (Schema) mp.getAdditionalProperties(); + return "(Map.Map String " + getTypeDeclaration(inner) + ")"; + } + return super.getTypeDeclaration(p); + } + + @Override + public String getSchemaType(Schema p) { + String openAPIType = super.getSchemaType(p); + + if (typeMapping.containsKey(openAPIType)) { + return typeMapping.get(openAPIType); + } else if (openAPIType == "object") { + return "A.Value"; + } else { + return toModelName(openAPIType); + } + } + + @Override + public String toInstantiationType(Schema p) { + if (p instanceof MapSchema) { + MapSchema ap = (MapSchema) p; + Schema additionalProperties2 = (Schema) ap.getAdditionalProperties(); + String type = additionalProperties2.getType(); + if (null == type) { + LOGGER.error("No Type defined for Additional Schema " + additionalProperties2 + "\n" // + + "\tIn Schema: " + p); + } + String inner = getSchemaType(additionalProperties2); + return "(Map.Map Text " + inner + ")"; + } else if (p instanceof ArraySchema) { + ArraySchema ap = (ArraySchema) p; + String inner = getSchemaType(ap.getItems()); + return inner; + } else { + return null; + } + } + + @Override + public void addOperationToGroup(String tag, String resourcePath, Operation operation, CodegenOperation op, Map> operations) { + + List opList = operations.get(tag); + if (opList == null) { + opList = new ArrayList(); + operations.put(tag, opList); + } + // check for operationId uniqueness + String uniqueName = op.operationId; + String uniqueNameType = toTypeName("Op", uniqueName); + int counter = 0; + + HashSet opIds = new HashSet<>(); + for (CodegenOperation o : opList) { + opIds.add(o.operationId); + } + while (opIds.contains(uniqueName) || + (allowNonUniqueOperationIds + ? modelTypeNames.contains(uniqueNameType) // only check for model conflicts + : typeNames.contains(uniqueNameType))) { // check globally across all types + uniqueName = op.operationId + counter; + uniqueNameType = toTypeName("Op", uniqueName); + counter++; + } + if (!op.operationId.equals(uniqueName)) { + LOGGER.warn("generated unique operationId `" + uniqueName + "`"); + } + op.operationId = uniqueName; + op.operationIdLowerCase = uniqueName.toLowerCase(); + op.operationIdCamelCase = DefaultCodegen.camelize(uniqueName); + op.operationIdSnakeCase = DefaultCodegen.underscore(uniqueName); + opList.add(op); + op.baseName = tag; + + // prevent aliasing/sharing of operation.vendorExtensions reference + op.vendorExtensions = new LinkedHashMap(); + + String operationType = toTypeName("Op", op.operationId); + op.vendorExtensions.put(X_OPERATION_TYPE, operationType); + typeNames.add(operationType); + + op.vendorExtensions.put(X_HADDOCK_PATH, String.format("%s %s", op.httpMethod, op.path.replace("/", "\\/"))); + op.vendorExtensions.put(X_HAS_BODY_OR_FORM_PARAM, op.getHasBodyParam() || op.getHasFormParams()); + + for (CodegenParameter param : op.allParams) { + param.vendorExtensions = new LinkedHashMap(); // prevent aliasing/sharing + param.vendorExtensions.put(X_OPERATION_TYPE, operationType); + param.vendorExtensions.put(X_IS_BODY_OR_FORM_PARAM, param.isBodyParam || param.isFormParam); + if (!StringUtils.isBlank(param.collectionFormat)) { + param.vendorExtensions.put(X_COLLECTION_FORMAT, mapCollectionFormat(param.collectionFormat)); + } + if (!param.required) { + op.vendorExtensions.put(X_HAS_OPTIONAL_PARAMS, true); + } + + if (typeMapping.containsKey(param.dataType) + || param.isMapContainer || param.isListContainer + || param.isPrimitiveType || param.isFile || param.isEnum) { + + String dataType = genEnums && param.isEnum ? param.datatypeWithEnum : param.dataType; + + String paramNameType = toDedupedModelName(toTypeName("Param", param.paramName), dataType, !param.isEnum); + param.vendorExtensions.put(X_PARAM_NAME_TYPE, paramNameType); + + HashMap props = new HashMap<>(); + props.put(X_IS_BODY_PARAM, param.isBodyParam); + addToUniques(X_NEWTYPE, paramNameType, dataType, props); + } + } + + processPathExpr(op); + + processProducesConsumes(op); + + processReturnType(op); + + } + + @Override + public List fromSecurity(Map schemes) { + List secs = super.fromSecurity(schemes); + for (CodegenSecurity sec : secs) { + String prefix = ""; + if (sec.isBasic) prefix = "AuthBasic"; + if (sec.isApiKey) prefix = "AuthApiKey"; + if (sec.isOAuth) prefix = "AuthOAuth"; + sec.name = prefix + toTypeName("", sec.name); + } + return secs; + } + + @Override + public Map postProcessOperations(Map objs) { + Map ret = super.postProcessOperations(objs); + + HashMap pathOps = (HashMap) ret.get("operations"); + ArrayList ops = (ArrayList) pathOps.get("operation"); + if (ops.size() > 0) { + ops.get(0).vendorExtensions.put(X_HAS_NEW_TAG, true); + } + + updateGlobalAdditionalProps(); + return ret; + } + + @Override + public Map postProcessAllModels(Map objs) { + updateGlobalAdditionalProps(); + return super.postProcessAllModels(objs); + } + + public void updateGlobalAdditionalProps() { + additionalProperties.put(X_HAS_UNKNOWN_MIME_TYPES, !unknownMimeTypes.isEmpty()); + + Collections.sort(unknownMimeTypes, new Comparator>() { + @Override + public int compare(Map o1, Map o2) { + return o1.get(MEDIA_TYPE).compareTo(o2.get(MEDIA_TYPE)); + } + }); + additionalProperties.put(X_UNKNOWN_MIME_TYPES, unknownMimeTypes); + + ArrayList> params = new ArrayList<>(uniqueParamNameTypes.values()); + Collections.sort(params, new Comparator>() { + @Override + public int compare(Map o1, Map o2) { + return + ((String) o1.get(X_PARAM_NAME_TYPE)) + .compareTo( + (String) o2.get(X_PARAM_NAME_TYPE)); + } + }); + additionalProperties.put(X_ALL_UNIQUE_PARAMS, params); + } + + @Override + public Map postProcessOperationsWithModels(Map objs, List allModels) { + for (Object o : allModels) { + HashMap h = (HashMap) o; + CodegenModel m = (CodegenModel) h.get("model"); + if (modelMimeTypes.containsKey(m.classname)) { + Set mimeTypes = modelMimeTypes.get(m.classname); + m.vendorExtensions.put(X_MIME_TYPES, mimeTypes); + if ((boolean) additionalProperties.get(PROP_GENERATE_FORM_URLENCODED_INSTANCES) && mimeTypes.contains("MimeFormUrlEncoded")) { + Boolean hasMimeFormUrlEncoded = true; + for (CodegenProperty v : m.vars) { + if (!(v.isPrimitiveType || v.isString || v.isDate || v.isDateTime)) { + hasMimeFormUrlEncoded = false; + } + } + if (hasMimeFormUrlEncoded) { + m.vendorExtensions.put(X_HAS_MIME_FORM_URL_ENCODED, true); + } + } + } + + } + return objs; + } + + @Override + public CodegenModel fromModel(String name, Schema mod, Map allDefinitions) { + CodegenModel model = super.fromModel(name, mod, allDefinitions); + + while (typeNames.contains(model.classname)) { + model.classname = generateNextName(model.classname); + } + typeNames.add(model.classname); + modelTypeNames.add(model.classname); + + // From the model name, compute the prefix for the fields. + String prefix = StringUtils.uncapitalize(model.classname); + for (CodegenProperty prop : model.vars) { + prop.name = toVarName(prefix, prop.name); + } + + return model; + } + + @Override + public String escapeReservedWord(String name) { + if (this.reservedWordsMappings().containsKey(name)) { + return this.reservedWordsMappings().get(name); + } + return "_" + name; + } + + @Override + public String escapeQuotationMark(String input) { + // remove " to avoid code injection + return input.replace("\"", ""); + } + + @Override + public String escapeUnsafeCharacters(String input) { + return input.replace("{-", "{_-").replace("-}", "-_}"); + } + + @Override + public boolean isDataTypeFile(String dataType) { + return dataType != null && dataType.equals("FilePath"); + } + + @Override + public boolean isDataTypeBinary(final String dataType) { + return dataType != null && dataType.equals("B.ByteString"); + } + + private void processReturnType(CodegenOperation op) { + String returnType = op.returnType; + if (returnType == null || returnType.equals("null")) { + if (op.hasProduces) { + returnType = "res"; + op.vendorExtensions.put(X_HAS_UNKNOWN_RETURN, true); + } else { + returnType = "NoContent"; + if (!op.vendorExtensions.containsKey(X_INLINE_ACCEPT)) { + SetNoContent(op, X_INLINE_ACCEPT); + } + } + } + if (returnType.indexOf(" ") >= 0) { + returnType = "(" + returnType + ")"; + } + op.vendorExtensions.put(X_RETURN_TYPE, returnType); + } + + private void processProducesConsumes(CodegenOperation op) { + if (!(Boolean) op.vendorExtensions.get(X_HAS_BODY_OR_FORM_PARAM)) { + SetNoContent(op, X_INLINE_CONTENT_TYPE); + } + if (op.hasConsumes) { + for (Map m : op.consumes) { + processMediaType(op, m); + processInlineConsumesContentType(op, m); + + } + if (isMultipartOperation(op.consumes)) { + op.isMultipart = Boolean.TRUE; + } + } + if (op.hasProduces) { + for (Map m : op.produces) { + processMediaType(op, m); + processInlineProducesContentType(op, m); + } + } + } + + private void processInlineConsumesContentType(CodegenOperation op, Map m) { + if (op.vendorExtensions.containsKey(X_INLINE_CONTENT_TYPE)) return; + if ((boolean) additionalProperties.get(PROP_INLINE_MIME_TYPES) + && op.consumes.size() == 1 + && op.consumes.get(0).get(X_MEDIA_DATA_TYPE) != MIME_ANY + && op.consumes.get(0).get(X_MEDIA_DATA_TYPE) != MIME_NO_CONTENT) { + op.vendorExtensions.put(X_INLINE_CONTENT_TYPE, m); + for (CodegenParameter param : op.allParams) { + if (param.isBodyParam && param.required) { + param.vendorExtensions.put(X_INLINE_CONTENT_TYPE, m); + } + } + } + } + + private void processInlineProducesContentType(CodegenOperation op, Map m) { + if ((boolean) additionalProperties.get(PROP_INLINE_MIME_TYPES) + && op.produces.size() == 1 + && op.produces.get(0).get(X_MEDIA_DATA_TYPE) != MIME_ANY + && op.produces.get(0).get(X_MEDIA_DATA_TYPE) != MIME_NO_CONTENT) { + op.vendorExtensions.put(X_INLINE_ACCEPT, m); + } + } + + private void SetNoContent(CodegenOperation op, String inlineExtentionName) { + Map m = new HashMap<>(); + m.put(X_MEDIA_DATA_TYPE, MIME_NO_CONTENT); + op.vendorExtensions.put(inlineExtentionName, m); + } + + private String toDedupedModelName(String paramNameType, String dataType, Boolean appendDataType) { + if (appendDataType + && uniqueParamNameTypes.containsKey(paramNameType) + && !isDuplicate(paramNameType, dataType)) { + paramNameType = paramNameType + dataType; + } + + while (typeNames.contains(paramNameType)) { + if (isDuplicate(paramNameType, dataType)) { + break; + } + paramNameType = generateNextName(paramNameType); + } + + typeNames.add(paramNameType); + modelTypeNames.add(paramNameType); + return paramNameType; + } + + public Boolean isDuplicate(String paramNameType, String dataType) { + Map lastParam = this.uniqueParamNameTypes.get(paramNameType); + if (lastParam != null) { + String comparisonKey = lastParam.containsKey(X_ENUM) ? X_ENUM_VALUES : X_DATA_TYPE; + String lastParamDataType = (String) lastParam.get(comparisonKey); + if (lastParamDataType != null && lastParamDataType.equals(dataType)) { + return true; + } + } + return false; + } + + private Pair isDuplicateEnumValues(String enumValues) { + for (Map vs : uniqueParamNameTypes.values()) { + if (enumValues.equals(vs.get(X_ENUM_VALUES))) { + return Pair.of(true, (String) vs.get(X_PARAM_NAME_TYPE)); + } + } + return Pair.of(false, null); + } + + + private void addToUniques(String xGroup, String paramNameType, String dataType, Map props) { + HashMap m = new HashMap<>(); + m.put(X_PARAM_NAME_TYPE, paramNameType); + m.put(X_DATA_TYPE, dataType); + m.put(xGroup, true); + m.putAll(props); + uniqueParamNameTypes.put(paramNameType, m); + } + + private void addEnumToUniques(String paramNameType, String datatype, String enumValues, Map allowableValues, String description) { + HashMap props = new HashMap<>(); + props.put("allowableValues", allowableValues); + if (StringUtils.isNotBlank(description)) { + props.put("description", description); + } + props.put(X_ENUM_VALUES, enumValues); + addToUniques(X_ENUM, paramNameType, datatype, props); + additionalProperties.put(X_HAS_ENUM_SECTION, true); + } + + + // build the parameterized path segments, according to pathParams + private void processPathExpr(CodegenOperation op) { + String xPath = "[\"" + escapeText(op.path) + "\"]"; + if (op.getHasPathParams()) { + for (CodegenParameter param : op.pathParams) { + xPath = xPath.replaceAll("\\{" + param.baseName + "\\}", "\",toPath " + param.paramName + ",\""); + } + xPath = xPath.replaceAll(",\"\",", ","); + xPath = xPath.replaceAll("\"\",", ","); + xPath = xPath.replaceAll(",\"\"", ","); + xPath = xPath.replaceAll("^\\[,", "["); + xPath = xPath.replaceAll(",\\]$", "]"); + } + op.vendorExtensions.put(X_PATH, xPath); + } + + + private void processMediaType(CodegenOperation op, Map m) { + String mediaType = m.get(MEDIA_TYPE); + + if (StringUtils.isBlank(mediaType)) return; + + String mimeType = getMimeDataType(mediaType); + typeNames.add(mimeType); + m.put(X_MEDIA_DATA_TYPE, mimeType); + if (isJsonMimeType(mediaType)) { + m.put(X_MEDIA_IS_JSON, "true"); + } + if (isWildcardMimeType(mediaType)) { + m.put(X_MEDIA_IS_WILDCARD, "true"); + } + if (!knownMimeDataTypes.containsValue(mimeType) && !unknownMimeTypesContainsType(mimeType)) { + unknownMimeTypes.add(m); + } + for (CodegenParameter param : op.allParams) { + if (param.isBodyParam || param.isFormParam && (!param.isPrimitiveType && !param.isListContainer && !param.isMapContainer)) { + Set mimeTypes = modelMimeTypes.containsKey(param.dataType) ? modelMimeTypes.get(param.dataType) : new HashSet(); + mimeTypes.add(mimeType); + modelMimeTypes.put(param.dataType, mimeTypes); + } + } + } + + private Boolean unknownMimeTypesContainsType(String mimeType) { + for (Map m : unknownMimeTypes) { + String mimeType0 = m.get(X_MEDIA_DATA_TYPE); + if (mimeType0 != null && mimeType0.equals(mimeType)) { + return true; + } + } + + return false; + } + + public String firstLetterToUpper(String word) { + if (word.length() == 0) { + return word; + } else if (word.length() == 1) { + return word.substring(0, 1).toUpperCase(); + } else { + return word.substring(0, 1).toUpperCase() + word.substring(1); + } + } + + public String firstLetterToLower(String word) { + if (word.length() == 0) { + return word; + } else if (word.length() == 1) { + return word.substring(0, 1).toLowerCase(); + } else { + return word.substring(0, 1).toLowerCase() + word.substring(1); + } + } + + private String mapCollectionFormat(String collectionFormat) { + switch (collectionFormat) { + case "csv": + return "CommaSeparated"; + case "tsv": + return "TabSeparated"; + case "ssv": + return "SpaceSeparated"; + case "pipes": + return "PipeSeparated"; + case "multi": + return "MultiParamArray"; + default: + throw new UnsupportedOperationException(); + } + } + + private String getMimeDataType(String mimeType) { + if (StringUtils.isBlank(mimeType)) { + return MIME_NO_CONTENT; + } + if (knownMimeDataTypes.containsKey(mimeType)) { + return knownMimeDataTypes.get(mimeType); + } + String shortenedName = mimeType.replaceFirst("application/", ""); + return "Mime" + toTypeName("", shortenedName); + } + + private static String generateNextName(String name) { + Pattern pattern = Pattern.compile("\\d+\\z"); + Matcher matcher = pattern.matcher(name); + if (matcher.find()) { + String numStr = matcher.group(); + int num = Integer.parseInt(numStr) + 1; + return name.substring(0, name.length() - numStr.length()) + num; + } else { + return name + "2"; + } + } + + private static boolean isMultipartOperation(List> consumes) { + for (Map consume : consumes) { + if (consume != null) { + if ("multipart/form-data".equals(consume.get(MEDIA_TYPE))) { + return true; + } + } + } + return false; + } + + @Override + public String toVarName(String name) { + return toVarName("", name); + } + + public String toVarName(String prefix, String name) { + Boolean hasPrefix = !StringUtils.isBlank(prefix); + name = underscore(sanitizeName(name.replaceAll("-", "_"))); + name = camelize(name, !hasPrefix); + if (hasPrefix) { + return prefix + name; + } else { + if (name.matches("^\\d.*")) + name = escapeReservedWord(name); + if (isReservedWord(name)) + name = escapeReservedWord(name); + return name; + } + } + + @Override + public String toParamName(String name) { + return toVarName(name); + } + + @Override + public String toModelName(String name) { + return toTypeName("Model", name); + } + + @Override + public String toModelFilename(String name) { + return toTypeName("Model", name); + } + + public String toApiName(String name) { + if (name.length() == 0) { + return "Default"; + } + return toTypeName("Api", name); + } + + @Override + public String toApiFilename(String name) { + return toTypeName("Api", name); + } + + @Override + public String apiFileFolder() { + return outputFolder + File.separator + this.modulePath + File.separator + "API"; + } + + public String toTypeName(String prefix, String name) { + name = escapeIdentifier(prefix, camelize(sanitizeName(name))); + return name; + } + + @Override + public String toOperationId(String operationId) { + if (StringUtils.isEmpty(operationId)) { + throw new RuntimeException("Empty method/operation name (operationId) not allowed"); + } + operationId = escapeIdentifier("op", camelize(sanitizeName(operationId), true)); + return operationId; + } + + public String escapeIdentifier(String prefix, String name) { + if (StringUtils.isBlank(prefix)) return name; + + if (isReservedWord(name)) { + name = prefix + name; + } + if (name.matches("^\\d.*")) { + name = prefix + name; // e.g. 200Response => Model200Response (after camelize) + } + if (languageSpecificPrimitives.contains(name)) { + name = prefix + name; + } + if (typeMapping.containsValue(name)) { + name = prefix + name; + } + return name; + } + + static boolean isJsonMimeType(String mime) { + return mime != null && JSON_MIME_PATTERN.matcher(mime).matches(); + } + + static boolean isWildcardMimeType(String mime) { + return mime != null && mime.equals("*/*"); + } + + @Override + public String toDefaultValue(Schema p) { + if (p instanceof StringSchema) { + StringSchema dp = (StringSchema) p; + if (dp.getDefault() != null) { + return "\"" + escapeText(dp.getDefault()) + "\""; + } + } else if (p instanceof BooleanSchema) { + BooleanSchema dp = (BooleanSchema) p; + if (dp.getDefault() != null) { + if (dp.getDefault().toString().equalsIgnoreCase("false")) + return "False"; + else + return "True"; + } + } + + return null; + } + + @Override + public Map postProcessModels(Map objs) { + List models = (List) objs.get("models"); + for (Object _mo : models) { + Map mo = (Map) _mo; + CodegenModel cm = (CodegenModel) mo.get("model"); + cm.isEnum = genEnums && cm.isEnum; + if (cm.isAlias) { + cm.vendorExtensions.put(X_DATA_TYPE, cm.dataType); + } + for (CodegenProperty var : cm.vars) { + String datatype = genEnums && !StringUtils.isBlank(var.datatypeWithEnum) + ? var.datatypeWithEnum + : var.datatype; + var.vendorExtensions.put(X_DATA_TYPE, datatype); + } + } + return postProcessModelsEnum(objs); + } + + @Override + public Map postProcessModelsEnum(Map objs) { + Map objsEnum = super.postProcessModelsEnum(objs); + if (genEnums) { + List models = (List) objsEnum.get("models"); + for (Object _mo : models) { + Map mo = (Map) _mo; + CodegenModel cm = (CodegenModel) mo.get("model"); + if (cm.isEnum && cm.allowableValues != null) { + updateAllowableValuesNames(cm.classname, cm.allowableValues); + addEnumToUniques(cm.classname, cm.dataType, cm.allowableValues.values().toString(), cm.allowableValues, cm.description); + } + } + } + return objsEnum; + } + + @Override + protected void updateDataTypeWithEnumForMap(CodegenProperty property) { + CodegenProperty baseItem = property.items; + while (baseItem != null && (Boolean.TRUE.equals(baseItem.isMapContainer) || Boolean.TRUE.equals(baseItem.isListContainer))) { + baseItem = baseItem.items; + } + if (baseItem != null) { + + // this replacement is/needs to be language-specific + property.datatypeWithEnum = property.datatypeWithEnum.replace(baseItem.baseType + ")", toEnumName(baseItem) + ")"); + + property.enumName = toEnumName(property); + if (property.defaultValue != null) { + property.defaultValue = property.defaultValue.replace(", " + property.items.baseType, ", " + toEnumName(property.items)); + } + } + } + + @Override + public String toEnumName(CodegenProperty var) { + if (!genEnums) return super.toEnumName(var); + + if (var.items != null && var.items.isEnum) { + return toEnumName(var.items); + } + String paramNameType = "E'" + toTypeName("", var.name); + String enumValues = var._enum.toString(); + + Pair duplicateEnum = isDuplicateEnumValues(enumValues); + if (duplicateEnum.getLeft()) { + paramNameType = duplicateEnum.getRight(); + } else { + paramNameType = toDedupedModelName(paramNameType, enumValues, false); + var.datatypeWithEnum = paramNameType; + updateCodegenPropertyEnum(var); + addEnumToUniques(paramNameType, var.datatype, enumValues, var.allowableValues, var.description); + } + + return paramNameType; + } + + @Override + public void updateCodegenPropertyEnum(CodegenProperty var) { + super.updateCodegenPropertyEnum(var); + if (!genEnums) return; + updateCodegenPropertyEnumValues(var, var.datatypeWithEnum); + } + + public void updateCodegenPropertyEnumValues(CodegenProperty var, String paramNameType) { + if (var.items != null && var.items.allowableValues != null) { + updateCodegenPropertyEnumValues(var.items, var.items.datatypeWithEnum); + return; + } + if (var.isEnum && var.allowableValues != null) { + updateAllowableValuesNames(paramNameType, var.allowableValues); + } + } + + private void updateAllowableValuesNames(String paramNameType, Map allowableValues) { + if (allowableValues == null) { + return; + } + for (Map enumVar : (List>) allowableValues.get("enumVars")) { + enumVar.put("name", paramNameType + enumVar.get("name")); + } + } + + @Override + public String toEnumVarName(String value, String datatype) { + if (!genEnums) return super.toEnumVarName(value, datatype); + + List num = new ArrayList<>(Arrays.asList("integer", "int", "double", "long", "float")); + if (value.length() == 0) { + return "'Empty"; + } + + // for symbol, e.g. $, # + if (getSymbolName(value) != null) { + return "'" + StringUtils.capitalize(sanitizeName(getSymbolName(value))); + } + + // number + if (num.contains(datatype.toLowerCase())) { + String varName = "Num" + value; + varName = varName.replaceAll("-", "Minus_"); + varName = varName.replaceAll("\\+", "Plus_"); + varName = varName.replaceAll("\\.", "_Dot_"); + return "'" + StringUtils.capitalize(sanitizeName(varName)); + } + + return "'" + StringUtils.capitalize(sanitizeName(value)); + } + + @Override + public String toEnumValue(String value, String datatype) { + List num = new ArrayList<>(Arrays.asList("integer", "int", "double", "long", "float")); + if (num.contains(datatype.toLowerCase())) { + return value; + } else { + return "\"" + escapeText(value) + "\""; + } + } + + // override with any special text escaping logic + @SuppressWarnings("static-method") + public String escapeText(String input) { + if (input == null) { + return input; + } + + // remove \t, \n, \r + // replace \ with \\ + // replace " with \" + // outter unescape to retain the original multi-byte characters + // finally escalate characters avoiding code injection + return escapeUnsafeCharacters( + StringEscapeUtils.unescapeJava( + StringEscapeUtils.escapeJava(input) + .replace("\\/", "/")) + .replaceAll("[\\t\\n\\r]", " ") + .replace("\\", "\\\\") + .replace("\"", "\\\"")); + } +} 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 a3ee838eea5..e4b3a329016 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 @@ -9,6 +9,7 @@ org.openapitools.codegen.languages.DartClientCodegen org.openapitools.codegen.languages.ElixirClientCodegen org.openapitools.codegen.languages.KotlinClientCodegen org.openapitools.codegen.languages.KotlinServerCodegen +org.openapitools.codegen.languages.HaskellHttpClientCodegen org.openapitools.codegen.languages.HaskellServantCodegen org.openapitools.codegen.languages.JavascriptClientCodegen org.openapitools.codegen.languages.LuaClientCodegen From 21ce66509ea97c12f02bf34d340960ac0775b0ba Mon Sep 17 00:00:00 2001 From: wing328 Date: Thu, 29 Mar 2018 16:57:16 +0800 Subject: [PATCH 27/29] add cpp server generator --- .../languages/CppPistacheServerCodegen.java | 400 ++++++++++++++++++ ...tCodegen.java => CppQt5ClientCodegen.java} | 4 +- .../languages/HaskellHttpClientCodegen.java | 2 +- .../org.openapitools.codegen.CodegenConfig | 8 +- 4 files changed, 408 insertions(+), 6 deletions(-) create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppPistacheServerCodegen.java rename modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/{Qt5CPPClientCodegen.java => CppQt5ClientCodegen.java} (99%) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppPistacheServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppPistacheServerCodegen.java new file mode 100644 index 00000000000..295196387d3 --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppPistacheServerCodegen.java @@ -0,0 +1,400 @@ +package org.openapitools.codegen.languages; + + +import java.io.File; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import io.swagger.v3.parser.util.SchemaTypeUtil; +import org.apache.commons.lang3.StringUtils; +import org.openapitools.codegen.*; +import io.swagger.v3.oas.models.security.SecurityScheme; +import io.swagger.v3.oas.models.*; +import io.swagger.v3.oas.models.media.*; +import io.swagger.v3.oas.models.responses.ApiResponse; +import io.swagger.v3.oas.models.parameters.*; +import io.swagger.v3.core.util.Yaml; + +public class CppPistacheServerCodegen extends AbstractCppCodegen { + protected String implFolder = "impl"; + + @Override + public CodegenType getTag() { + return CodegenType.SERVER; + } + + @Override + public String getName() { + return "pistache-server"; + } + + @Override + public String getHelp() { + return "Generates a C++ API server (based on Pistache)"; + } + + public CppPistacheServerCodegen() { + super(); + + apiPackage = "io.swagger.server.api"; + modelPackage = "io.swagger.server.model"; + + modelTemplateFiles.put("model-header.mustache", ".h"); + modelTemplateFiles.put("model-source.mustache", ".cpp"); + + apiTemplateFiles.put("api-header.mustache", ".h"); + apiTemplateFiles.put("api-source.mustache", ".cpp"); + apiTemplateFiles.put("api-impl-header.mustache", ".h"); + apiTemplateFiles.put("api-impl-source.mustache", ".cpp"); + apiTemplateFiles.put("main-api-server.mustache", ".cpp"); + + embeddedTemplateDir = templateDir = "pistache-server"; + + cliOptions.clear(); + + reservedWords = new HashSet<>(); + + supportingFiles.add(new SupportingFile("modelbase-header.mustache", "model", "ModelBase.h")); + supportingFiles.add(new SupportingFile("modelbase-source.mustache", "model", "ModelBase.cpp")); + supportingFiles.add(new SupportingFile("cmake.mustache", "", "CMakeLists.txt")); + supportingFiles.add(new SupportingFile("README.mustache", "", "README.md")); + + languageSpecificPrimitives = new HashSet( + Arrays.asList("int", "char", "bool", "long", "float", "double", "int32_t", "int64_t")); + + typeMapping = new HashMap(); + typeMapping.put("date", "std::string"); + typeMapping.put("DateTime", "std::string"); + typeMapping.put("string", "std::string"); + typeMapping.put("integer", "int32_t"); + typeMapping.put("long", "int64_t"); + typeMapping.put("boolean", "bool"); + typeMapping.put("array", "std::vector"); + typeMapping.put("map", "std::map"); + typeMapping.put("file", "std::string"); + typeMapping.put("object", "Object"); + typeMapping.put("binary", "std::string"); + typeMapping.put("number", "double"); + typeMapping.put("UUID", "std::string"); + + super.importMapping = new HashMap(); + importMapping.put("std::vector", "#include "); + importMapping.put("std::map", "#include "); + importMapping.put("std::string", "#include "); + importMapping.put("Object", "#include \"Object.h\""); + } + + @Override + public void processOpts() { + super.processOpts(); + + additionalProperties.put("modelNamespaceDeclarations", modelPackage.split("\\.")); + additionalProperties.put("modelNamespace", modelPackage.replaceAll("\\.", "::")); + additionalProperties.put("apiNamespaceDeclarations", apiPackage.split("\\.")); + additionalProperties.put("apiNamespace", apiPackage.replaceAll("\\.", "::")); + } + + /** + * 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) { + return "_" + name; // add an underscore to the name + } + + @Override + public String toModelImport(String name) { + if (importMapping.containsKey(name)) { + return importMapping.get(name); + } else { + return "#include \"" + name + ".h\""; + } + } + + @Override + public CodegenModel fromModel(String name, Schema model, Map allDefinitions) { + CodegenModel codegenModel = super.fromModel(name, model, allDefinitions); + + Set oldImports = codegenModel.imports; + codegenModel.imports = new HashSet<>(); + for (String imp : oldImports) { + String newImp = toModelImport(imp); + if (!newImp.isEmpty()) { + codegenModel.imports.add(newImp); + } + } + + return codegenModel; + } + + @Override + public CodegenOperation fromOperation(String path, String httpMethod, Operation operation, + Map definitions, OpenAPI openAPI) { + CodegenOperation op = super.fromOperation(path, httpMethod, operation, definitions, openAPI); + + if (operation.getResponses() != null && !operation.getResponses().isEmpty()) { + ApiResponse apiResponse = findMethodResponse(operation.getResponses()); + + if (apiResponse != null) { + Schema response = getSchemaFromResponse(apiResponse); + if (response != null) { + CodegenProperty cm = fromProperty("response", response); + op.vendorExtensions.put("x-codegen-response", cm); + if (cm.datatype == "HttpContent") { + op.vendorExtensions.put("x-codegen-response-ishttpcontent", true); + } + } + } + } + + String pathForPistache = path.replaceAll("\\{(.*?)}", ":$1"); + op.vendorExtensions.put("x-codegen-pistache-path", pathForPistache); + + return op; + } + + @SuppressWarnings("unchecked") + @Override + public Map postProcessOperations(Map objs) { + Map operations = (Map) objs.get("operations"); + String classname = (String) operations.get("classname"); + operations.put("classnameSnakeUpperCase", DefaultCodegen.underscore(classname).toUpperCase()); + operations.put("classnameSnakeLowerCase", DefaultCodegen.underscore(classname).toLowerCase()); + + List operationList = (List) operations.get("operation"); + for (CodegenOperation op : operationList) { + boolean consumeJson = false; + boolean isParsingSupported = true; + if (op.bodyParam != null) { + if (op.bodyParam.vendorExtensions == null) { + op.bodyParam.vendorExtensions = new HashMap<>(); + } + + op.bodyParam.vendorExtensions.put("x-codegen-pistache-isStringOrDate", op.bodyParam.isString || op.bodyParam.isDate); + } + if(op.consumes != null) { + for (Map consume : op.consumes) { + if (consume.get("mediaType") != null && consume.get("mediaType").equals("application/json")) { + consumeJson = true; + } + } + } + + op.httpMethod = op.httpMethod.substring(0, 1).toUpperCase() + op.httpMethod.substring(1).toLowerCase(); + + for(CodegenParameter param : op.allParams){ + if (param.isFormParam) isParsingSupported=false; + if (param.isFile) isParsingSupported=false; + if (param.isCookieParam) isParsingSupported=false; + + //TODO: This changes the info about the real type but it is needed to parse the header params + if (param.isHeaderParam) { + param.dataType = "Optional"; + param.baseType = "Optional"; + } else if(param.isQueryParam){ + if(param.isPrimitiveType) { + param.dataType = "Optional<" + param.dataType + ">"; + } else { + param.dataType = "Optional<" + param.baseType + ">"; + param.baseType = "Optional<" + param.baseType + ">"; + } + } + } + + if (op.vendorExtensions == null) { + op.vendorExtensions = new HashMap<>(); + } + op.vendorExtensions.put("x-codegen-pistache-consumesJson", consumeJson); + op.vendorExtensions.put("x-codegen-pistache-isParsingSupported", isParsingSupported); + } + + return objs; + } + + @Override + public String toModelFilename(String name) { + return initialCaps(name); + } + + @Override + public String apiFilename(String templateName, String tag) { + String result = super.apiFilename(templateName, tag); + + if ( templateName.endsWith("impl-header.mustache") ) { + int ix = result.lastIndexOf('/'); + result = result.substring(0, ix) + result.substring(ix, result.length() - 2) + "Impl.h"; + result = result.replace(apiFileFolder(), implFileFolder()); + } else if ( templateName.endsWith("impl-source.mustache") ) { + int ix = result.lastIndexOf('/'); + result = result.substring(0, ix) + result.substring(ix, result.length() - 4) + "Impl.cpp"; + result = result.replace(apiFileFolder(), implFileFolder()); + } else if ( templateName.endsWith("api-server.mustache") ) { + int ix = result.lastIndexOf('/'); + result = result.substring(0, ix) + result.substring(ix, result.length() - 4) + "MainServer.cpp"; + result = result.replace(apiFileFolder(), outputFolder); + } + return result; + } + + @Override + public String toApiFilename(String name) { + return initialCaps(name) + "Api"; + } + + /** + * Optional - type declaration. This is a String which is used by the + * templates to instantiate your types. There is typically special handling + * for different property types + * + * @return a string value used as the `dataType` field for model templates, + * `returnType` for api templates + */ + @Override + public String getTypeDeclaration(Schema p) { + String swaggerType = getSchemaType(p); + + if (p instanceof ArraySchema) { + ArraySchema ap = (ArraySchema) p; + Schema inner = ap.getItems(); + return getSchemaType(p) + "<" + getTypeDeclaration(inner) + ">"; + } + if (p instanceof MapSchema) { + MapSchema mp = (MapSchema) p; + Schema inner = (Schema) mp.getAdditionalProperties(); + return getSchemaType(p) + ""; + } + if (p instanceof StringSchema || p instanceof DateSchema + || p instanceof DateTimeSchema || p instanceof FileSchema + || languageSpecificPrimitives.contains(swaggerType)) { + return toModelName(swaggerType); + } + + return "std::shared_ptr<" + swaggerType + ">"; + } + + @Override + public String toDefaultValue(Schema p) { + if (p instanceof StringSchema) { + return "\"\""; + } else if (p instanceof BooleanSchema) { + return "false"; + } else if (p instanceof DateSchema) { + return "\"\""; + } else if (p instanceof DateTimeSchema) { + return "\"\""; + } else if (p instanceof NumberSchema) { + if (SchemaTypeUtil.FLOAT_FORMAT.equals(p.getFormat())) { + return "0.0f"; + } + return "0.0"; + } else if (p instanceof IntegerSchema) { + if (SchemaTypeUtil.INTEGER64_FORMAT.equals(p.getFormat())) { + return "0L"; + } + return "0"; + } else if (p instanceof MapSchema) { + MapSchema ap = (MapSchema) p; + String inner = getSchemaType((Schema) ap.getAdditionalProperties()); + return "std::map()"; + } else if (p instanceof ArraySchema) { + ArraySchema ap = (ArraySchema) p; + String inner = getSchemaType(ap.getItems()); + if (!languageSpecificPrimitives.contains(inner)) { + inner = "std::shared_ptr<" + inner + ">"; + } + return "std::vector<" + inner + ">()"; + } else if (!StringUtils.isEmpty(p.get$ref())) { // model + return "new " + toModelName(p.get$ref()) + "()"; + } + return "nullptr"; + } + + @Override + public void postProcessParameter(CodegenParameter parameter) { + super.postProcessParameter(parameter); + + boolean isPrimitiveType = parameter.isPrimitiveType == Boolean.TRUE; + boolean isListContainer = parameter.isListContainer == Boolean.TRUE; + boolean isString = parameter.isString == Boolean.TRUE; + + if (!isPrimitiveType && !isListContainer && !isString && !parameter.dataType.startsWith("std::shared_ptr")) { + parameter.dataType = "std::shared_ptr<" + parameter.dataType + ">"; + } + } + + /** + * Location to write model files. You can use the modelPackage() as defined + * when the class is instantiated + */ + public String modelFileFolder() { + return (outputFolder + "/model").replace("/", File.separator); + } + + /** + * Location to write api files. You can use the apiPackage() as defined when + * the class is instantiated + */ + @Override + public String apiFileFolder() { + return (outputFolder + "/api").replace("/", File.separator); + } + + private String implFileFolder() { + return (outputFolder + "/" + implFolder).replace("/", File.separator); + } + + /** + * Optional - swagger type conversion. This is used to map swagger types in + * a `Schema` into either language specific types via `typeMapping` or + * into complex models if there is not a mapping. + * + * @return a string value of the type or complex model for this property + */ + @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 toModelName(String type) { + if (typeMapping.keySet().contains(type) || typeMapping.values().contains(type) + || importMapping.values().contains(type) || defaultIncludes.contains(type) + || languageSpecificPrimitives.contains(type)) { + return type; + } else { + return Character.toUpperCase(type.charAt(0)) + type.substring(1); + } + } + + @Override + public String toApiName(String type) { + return Character.toUpperCase(type.charAt(0)) + type.substring(1) + "Api"; + } + + @Override + public String escapeQuotationMark(String input) { + // remove " to avoid code injection + return input.replace("\"", ""); + } + + @Override + public String escapeUnsafeCharacters(String input) { + return input.replace("*/", "*_/").replace("/*", "/_*"); + } +} diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Qt5CPPClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppQt5ClientCodegen.java similarity index 99% rename from modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Qt5CPPClientCodegen.java rename to modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppQt5ClientCodegen.java index e23bc88231f..311b7186d89 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Qt5CPPClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppQt5ClientCodegen.java @@ -15,7 +15,7 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; -public class Qt5CPPClientCodegen extends AbstractCppCodegen implements CodegenConfig { +public class CppQt5ClientCodegen extends AbstractCppCodegen implements CodegenConfig { public static final String CPP_NAMESPACE = "cppNamespace"; public static final String CPP_NAMESPACE_DESC = "C++ namespace (convention: name::space::for::api)."; public static final String OPTIONAL_PROJECT_FILE_DESC = "Generate client.pri."; @@ -30,7 +30,7 @@ public class Qt5CPPClientCodegen extends AbstractCppCodegen implements CodegenCo protected String cppNamespace = "Swagger"; protected boolean optionalProjectFileFlag = true; - public Qt5CPPClientCodegen() { + public CppQt5ClientCodegen() { super(); // set the output folder here diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/HaskellHttpClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/HaskellHttpClientCodegen.java index 4729cb23c6a..2eaf33370a3 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/HaskellHttpClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/HaskellHttpClientCodegen.java @@ -4,8 +4,8 @@ import java.util.*; import java.util.regex.Pattern; import java.io.File; -import io.swagger.v3.oas.models.security.SecurityScheme; import org.openapitools.codegen.*; +import io.swagger.v3.oas.models.security.SecurityScheme; import io.swagger.v3.oas.models.*; import io.swagger.v3.oas.models.media.*; import io.swagger.v3.oas.models.parameters.*; 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 e4b3a329016..c7236138571 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 @@ -4,7 +4,9 @@ org.openapitools.codegen.languages.AkkaScalaClientCodegen org.openapitools.codegen.languages.BashClientCodegen org.openapitools.codegen.languages.ClojureClientCodegen org.openapitools.codegen.languages.ConfluenceWikiCodegen +org.openapitools.codegen.languages.CppQt5ClientCodegen org.openapitools.codegen.languages.CppRestClientCodegen +org.openapitools.codegen.languages.CppPistacheServerCodegen org.openapitools.codegen.languages.DartClientCodegen org.openapitools.codegen.languages.ElixirClientCodegen org.openapitools.codegen.languages.KotlinClientCodegen @@ -13,14 +15,14 @@ org.openapitools.codegen.languages.HaskellHttpClientCodegen org.openapitools.codegen.languages.HaskellServantCodegen org.openapitools.codegen.languages.JavascriptClientCodegen org.openapitools.codegen.languages.LuaClientCodegen +org.openapitools.codegen.languages.ObjcClientCodegen +org.openapitools.codegen.languages.PerlClientCodegen +org.openapitools.codegen.languages.PhpClientCodegen org.openapitools.codegen.languages.PhpLumenServerCodegen org.openapitools.codegen.languages.PhpSlimServerCodegen org.openapitools.codegen.languages.PhpSilexServerCodegen org.openapitools.codegen.languages.PhpSymfonyServerCodegen org.openapitools.codegen.languages.PhpZendExpressivePathHandlerServerCodegen -org.openapitools.codegen.languages.ObjcClientCodegen -org.openapitools.codegen.languages.PerlClientCodegen -org.openapitools.codegen.languages.PhpClientCodegen org.openapitools.codegen.languages.PowerShellClientCodegen org.openapitools.codegen.languages.PythonClientCodegen org.openapitools.codegen.languages.PythonFlaskConnexionServerCodegen From 27404894d7414da95de1da83449b15794d536f04 Mon Sep 17 00:00:00 2001 From: wing328 Date: Thu, 29 Mar 2018 17:32:51 +0800 Subject: [PATCH 28/29] add cpp restbed generator --- .../languages/CppPistacheServerCodegen.java | 1 - .../languages/CppRestbedServerCodegen.java | 383 ++++++++++++++++++ .../org.openapitools.codegen.CodegenConfig | 1 + 3 files changed, 384 insertions(+), 1 deletion(-) create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppRestbedServerCodegen.java diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppPistacheServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppPistacheServerCodegen.java index 295196387d3..2422cf55c0b 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppPistacheServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppPistacheServerCodegen.java @@ -1,6 +1,5 @@ package org.openapitools.codegen.languages; - import java.io.File; import java.util.Arrays; import java.util.HashMap; diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppRestbedServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppRestbedServerCodegen.java new file mode 100644 index 00000000000..e9e798852b0 --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppRestbedServerCodegen.java @@ -0,0 +1,383 @@ +package org.openapitools.codegen.languages; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import io.swagger.v3.parser.util.SchemaTypeUtil; +import org.apache.commons.lang3.StringUtils; +import org.openapitools.codegen.*; +import io.swagger.v3.oas.models.security.SecurityScheme; +import io.swagger.v3.oas.models.*; +import io.swagger.v3.oas.models.media.*; +import io.swagger.v3.oas.models.responses.ApiResponse; +import io.swagger.v3.oas.models.parameters.*; +import io.swagger.v3.core.util.Yaml; + +public class CppRestbedServerCodegen extends AbstractCppCodegen { + + public static final String DECLSPEC = "declspec"; + public static final String DEFAULT_INCLUDE = "defaultInclude"; + + protected String packageVersion = "1.0.0"; + protected String declspec = ""; + protected String defaultInclude = ""; + + public CppRestbedServerCodegen() { + super(); + + apiPackage = "io.swagger.server.api"; + modelPackage = "io.swagger.server.model"; + + modelTemplateFiles.put("model-header.mustache", ".h"); + modelTemplateFiles.put("model-source.mustache", ".cpp"); + + apiTemplateFiles.put("api-header.mustache", ".h"); + apiTemplateFiles.put("api-source.mustache", ".cpp"); + + embeddedTemplateDir = templateDir = "restbed"; + + cliOptions.clear(); + + // CLI options + addOption(CodegenConstants.MODEL_PACKAGE, "C++ namespace for models (convention: name.space.model).", + this.modelPackage); + addOption(CodegenConstants.API_PACKAGE, "C++ namespace for apis (convention: name.space.api).", + this.apiPackage); + addOption(CodegenConstants.PACKAGE_VERSION, "C++ package version.", this.packageVersion); + addOption(DECLSPEC, "C++ preprocessor to place before the class name for handling dllexport/dllimport.", + this.declspec); + addOption(DEFAULT_INCLUDE, + "The default include statement that should be placed in all headers for including things like the declspec (convention: #include \"Commons.h\" ", + this.defaultInclude); + + supportingFiles.add(new SupportingFile("gitignore.mustache", "", ".gitignore")); + supportingFiles.add(new SupportingFile("git_push.sh.mustache", "", "git_push.sh")); + supportingFiles.add(new SupportingFile("README.mustache", "", "README.md")); + + languageSpecificPrimitives = new HashSet( + Arrays.asList("int", "char", "bool", "long", "float", "double", "int32_t", "int64_t")); + + typeMapping = new HashMap(); + typeMapping.put("date", "std::string"); + typeMapping.put("DateTime", "std::string"); + typeMapping.put("string", "std::string"); + typeMapping.put("integer", "int32_t"); + typeMapping.put("long", "int64_t"); + typeMapping.put("boolean", "bool"); + typeMapping.put("array", "std::vector"); + typeMapping.put("map", "std::map"); + typeMapping.put("file", "std::string"); + typeMapping.put("object", "Object"); + typeMapping.put("binary", "restbed::Bytes"); + typeMapping.put("number", "double"); + typeMapping.put("UUID", "std::string"); + + super.importMapping = new HashMap(); + importMapping.put("std::vector", "#include "); + importMapping.put("std::map", "#include "); + importMapping.put("std::string", "#include "); + importMapping.put("Object", "#include \"Object.h\""); + importMapping.put("restbed::Bytes", "#include "); + } + + /** + * Configures the type of generator. + * + * @return the CodegenType for this generator + * @see io.swagger.codegen.CodegenType + */ + 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 + */ + public String getName() { + return "restbed"; + } + + /** + * Returns human-friendly help for the generator. Provide the consumer with + * help tips, parameters here + * + * @return A string value for the help message + */ + public String getHelp() { + return "Generates a C++ API Server with Restbed (https://github.com/Corvusoft/restbed)."; + } + + @Override + public void processOpts() { + super.processOpts(); + + if (additionalProperties.containsKey(DECLSPEC)) { + declspec = additionalProperties.get(DECLSPEC).toString(); + } + + if (additionalProperties.containsKey(DEFAULT_INCLUDE)) { + defaultInclude = additionalProperties.get(DEFAULT_INCLUDE).toString(); + } + + additionalProperties.put("modelNamespaceDeclarations", modelPackage.split("\\.")); + additionalProperties.put("modelNamespace", modelPackage.replaceAll("\\.", "::")); + additionalProperties.put("apiNamespaceDeclarations", apiPackage.split("\\.")); + additionalProperties.put("apiNamespace", apiPackage.replaceAll("\\.", "::")); + additionalProperties.put("declspec", declspec); + additionalProperties.put("defaultInclude", defaultInclude); + } + + /** + * 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) { + return "_" + name; // add an underscore to the name + } + + /** + * Location to write model files. You can use the modelPackage() as defined + * when the class is instantiated + */ + public String modelFileFolder() { + return (outputFolder + "/model").replace("/", File.separator); + } + + /** + * Location to write api files. You can use the apiPackage() as defined when + * the class is instantiated + */ + @Override + public String apiFileFolder() { + return (outputFolder + "/api").replace("/", File.separator); + } + + @Override + public String toModelImport(String name) { + if (importMapping.containsKey(name)) { + return importMapping.get(name); + } else { + return "#include \"" + name + ".h\""; + } + } + + @Override + public CodegenModel fromModel(String name, Schema model, Map allDefinitions) { + CodegenModel codegenModel = super.fromModel(name, model, allDefinitions); + + Set oldImports = codegenModel.imports; + codegenModel.imports = new HashSet(); + for (String imp : oldImports) { + String newImp = toModelImport(imp); + if (!newImp.isEmpty()) { + codegenModel.imports.add(newImp); + } + } + + return codegenModel; + } + + + @Override + public String toModelFilename(String name) { + return initialCaps(name); + } + + @Override + public String toApiFilename(String name) { + return initialCaps(name) + "Api"; + } + + @SuppressWarnings("unchecked") + @Override + public Map postProcessOperations(Map objs) { + Map operations = (Map) objs.get("operations"); + List operationList = (List) operations.get("operation"); + List newOpList = new ArrayList(); + for (CodegenOperation op : operationList) { + String path = new String(op.path); + + String[] items = path.split("/", -1); + String resourceNameCamelCase = ""; + op.path = ""; + for (String item : items) { + if (item.length() > 1) { + if (item.matches("^\\{(.*)\\}$")) { + String tmpResourceName = item.substring(1, item.length() - 1); + resourceNameCamelCase += Character.toUpperCase(tmpResourceName.charAt(0)) + tmpResourceName.substring(1); + item = item.substring(0, item.length() - 1); + item += ": .*}"; + } else { + resourceNameCamelCase += Character.toUpperCase(item.charAt(0)) + item.substring(1); + } + } else if (item.length() == 1) { + resourceNameCamelCase += Character.toUpperCase(item.charAt(0)); + } + op.path += item + "/"; + } + op.vendorExtensions.put("x-codegen-resourceName", resourceNameCamelCase); + boolean foundInNewList = false; + for (CodegenOperation op1 : newOpList) { + if (!foundInNewList) { + if (op1.path.equals(op.path)) { + foundInNewList = true; + List currentOtherMethodList = (List) op1.vendorExtensions.get("x-codegen-otherMethods"); + if (currentOtherMethodList == null) { + currentOtherMethodList = new ArrayList(); + } + op.operationIdCamelCase = op1.operationIdCamelCase; + currentOtherMethodList.add(op); + op1.vendorExtensions.put("x-codegen-otherMethods", currentOtherMethodList); + } + } + } + if (!foundInNewList) { + newOpList.add(op); + } + } + operations.put("operation", newOpList); + return objs; + } + + /** + * Optional - type declaration. This is a String which is used by the + * templates to instantiate your types. There is typically special handling + * for different property types + * + * @return a string value used as the `dataType` field for model templates, + * `returnType` for api templates + */ + @Override + public String getTypeDeclaration(Schema p) { + String swaggerType = getSchemaType(p); + + if (p instanceof ArraySchema) { + ArraySchema ap = (ArraySchema) p; + Schema inner = ap.getItems(); + return getSchemaType(p) + "<" + getTypeDeclaration(inner) + ">"; + } + if (p instanceof MapSchema) { + Schema inner = (Schema) p.getAdditionalProperties(); + return getSchemaType(p) + ""; + } + if (p instanceof StringSchema || p instanceof DateSchema + || p instanceof DateTimeSchema || p instanceof FileSchema + || languageSpecificPrimitives.contains(swaggerType)) { + return toModelName(swaggerType); + } + + return "std::shared_ptr<" + swaggerType + ">"; + } + + @Override + public String toDefaultValue(Schema p) { + if (p instanceof StringSchema) { + return "\"\""; + } else if (p instanceof BooleanSchema) { + return "false"; + } else if (p instanceof DateSchema) { + return "\"\""; + } else if (p instanceof DateTimeSchema) { + return "\"\""; + } else if (p instanceof NumberSchema) { + if (SchemaTypeUtil.FLOAT_FORMAT.equals(p.getFormat())) { + return "0.0f"; + } + return "0.0"; + } else if (p instanceof IntegerSchema) { + if (SchemaTypeUtil.INTEGER64_FORMAT.equals(p.getFormat())) { + return "0L"; + } + return "0"; + } else if (p instanceof MapSchema) { + MapSchema ap = (MapSchema) p; + String inner = getSchemaType((Schema) ap.getAdditionalProperties()); + return "std::map()"; + } else if (p instanceof ArraySchema) { + ArraySchema ap = (ArraySchema) p; + String inner = getSchemaType(ap.getItems()); + if (!languageSpecificPrimitives.contains(inner)) { + inner = "std::shared_ptr<" + inner + ">"; + } + return "std::vector<" + inner + ">()"; + } else if (!StringUtils.isEmpty(p.get$ref())) { + return "new " + toModelName(p.get$ref()) + "()"; + } + return "nullptr"; + } + + @Override + public void postProcessParameter(CodegenParameter parameter) { + super.postProcessParameter(parameter); + + boolean isPrimitiveType = parameter.isPrimitiveType == Boolean.TRUE; + boolean isListContainer = parameter.isListContainer == Boolean.TRUE; + boolean isString = parameter.isString == Boolean.TRUE; + + if (!isPrimitiveType && !isListContainer && !isString && !parameter.dataType.startsWith("std::shared_ptr")) { + parameter.dataType = "std::shared_ptr<" + parameter.dataType + ">"; + } + } + + /** + * Optional - swagger type conversion. This is used to map swagger types in + * a `Schema` into either language specific types via `typeMapping` or + * into complex models if there is not a mapping. + * + * @return a string value of the type or complex model for this property + */ + @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 toModelName(String type) { + if (typeMapping.keySet().contains(type) || typeMapping.values().contains(type) + || importMapping.values().contains(type) || defaultIncludes.contains(type) + || languageSpecificPrimitives.contains(type)) { + return type; + } else { + return Character.toUpperCase(type.charAt(0)) + type.substring(1); + } + } + + @Override + public String toApiName(String type) { + return Character.toUpperCase(type.charAt(0)) + type.substring(1) + "Api"; + } + + @Override + public String escapeQuotationMark(String input) { + // remove " to avoid code injection + return input.replace("\"", ""); + } + + @Override + public String escapeUnsafeCharacters(String input) { + return input.replace("*/", "*_/").replace("/*", "/_*"); + } + + +} 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 c7236138571..4207e825fd9 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 @@ -5,6 +5,7 @@ org.openapitools.codegen.languages.BashClientCodegen org.openapitools.codegen.languages.ClojureClientCodegen org.openapitools.codegen.languages.ConfluenceWikiCodegen org.openapitools.codegen.languages.CppQt5ClientCodegen +org.openapitools.codegen.languages.CppRestbedServerCodegen org.openapitools.codegen.languages.CppRestClientCodegen org.openapitools.codegen.languages.CppPistacheServerCodegen org.openapitools.codegen.languages.DartClientCodegen From ea6d26eb3bf84d03fe7ce049932cb9ba6b155b34 Mon Sep 17 00:00:00 2001 From: wing328 Date: Thu, 29 Mar 2018 18:39:19 +0800 Subject: [PATCH 29/29] add abstract cpp generator --- .../languages/AbstractCSharpCodegen.java | 948 ++++++++++++++++++ .../languages/CppRestbedServerCodegen.java | 1 - 2 files changed, 948 insertions(+), 1 deletion(-) create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractCSharpCodegen.java diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractCSharpCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractCSharpCodegen.java new file mode 100644 index 00000000000..74b235f1e73 --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractCSharpCodegen.java @@ -0,0 +1,948 @@ +package org.openapitools.codegen.languages; + +import com.google.common.collect.ImmutableMap; +import com.samskivert.mustache.Mustache; +import org.apache.commons.lang3.StringUtils; +import org.openapitools.codegen.*; +import org.openapitools.codegen.utils.*; +import org.openapitools.codegen.mustache.*; +import io.swagger.v3.oas.models.security.SecurityScheme; +import io.swagger.v3.oas.models.*; +import io.swagger.v3.oas.models.media.*; +import io.swagger.v3.oas.models.responses.ApiResponse; +import io.swagger.v3.oas.models.parameters.*; +import io.swagger.v3.core.util.Yaml; +import io.swagger.v3.parser.util.SchemaTypeUtil; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.util.*; + +public abstract class AbstractCSharpCodegen extends DefaultCodegen implements CodegenConfig { + + protected boolean optionalAssemblyInfoFlag = true; + protected boolean optionalProjectFileFlag = true; + protected boolean optionalEmitDefaultValue = false; + protected boolean optionalMethodArgumentFlag = true; + protected boolean useDateTimeOffsetFlag = false; + protected boolean useCollection = false; + protected boolean returnICollection = false; + protected boolean netCoreProjectFileFlag = false; + + protected String modelPropertyNaming = CodegenConstants.MODEL_PROPERTY_NAMING_TYPE.PascalCase.name(); + + protected String packageVersion = "1.0.0"; + protected String packageName = "IO.Swagger"; + protected String packageTitle = "Swagger Library"; + protected String packageProductName = "SwaggerLibrary"; + protected String packageDescription = "A library generated from a Swagger doc"; + protected String packageCompany = "Swagger"; + protected String packageCopyright = "No Copyright"; + protected String packageAuthors = "Swagger"; + + protected String interfacePrefix = "I"; + + protected String sourceFolder = "src"; + + // TODO: Add option for test folder output location. Nice to allow e.g. ./test instead of ./src. + // This would require updating relative paths (e.g. path to main project file in test project file) + protected String testFolder = sourceFolder; + + protected Set collectionTypes; + protected Set mapTypes; + + protected Logger LOGGER = LoggerFactory.getLogger(AbstractCSharpCodegen.class); + + public AbstractCSharpCodegen() { + super(); + + supportsInheritance = true; + + // C# does not use import mapping + importMapping.clear(); + + outputFolder = "generated-code" + File.separator + this.getName(); + embeddedTemplateDir = templateDir = this.getName(); + + collectionTypes = new HashSet( + Arrays.asList( + "IList", "List", + "ICollection", "Collection", + "IEnumerable") + ); + + mapTypes = new HashSet( + Arrays.asList("IDictionary") + ); + + // NOTE: C# uses camel cased reserved words, while models are title cased. We don't want lowercase comparisons. + reservedWords.addAll( + Arrays.asList( + // set "client" as a reserved word to avoid conflicts with IO.Swagger.Client + // this is a workaround and can be removed if c# api client is updated to use + // fully qualified name + "Client", "client", "parameter", + // local variable names in API methods (endpoints) + "localVarPath", "localVarPathParams", "localVarQueryParams", "localVarHeaderParams", + "localVarFormParams", "localVarFileParams", "localVarStatusCode", "localVarResponse", + "localVarPostBody", "localVarHttpHeaderAccepts", "localVarHttpHeaderAccept", + "localVarHttpContentTypes", "localVarHttpContentType", + "localVarStatusCode", + // C# reserved words + "abstract", "as", "base", "bool", "break", "byte", "case", "catch", "char", "checked", + "class", "const", "continue", "decimal", "default", "delegate", "do", "double", "else", + "enum", "event", "explicit", "extern", "false", "finally", "fixed", "float", "for", + "foreach", "goto", "if", "implicit", "in", "int", "interface", "internal", "is", "lock", + "long", "namespace", "new", "null", "object", "operator", "out", "override", "params", + "private", "protected", "public", "readonly", "ref", "return", "sbyte", "sealed", + "short", "sizeof", "stackalloc", "static", "string", "struct", "switch", "this", "throw", + "true", "try", "typeof", "uint", "ulong", "unchecked", "unsafe", "ushort", "using", + "virtual", "void", "volatile", "while") + ); + + // TODO: Either include fully qualified names here or handle in DefaultCodegen via lastIndexOf(".") search + languageSpecificPrimitives = new HashSet( + Arrays.asList( + "String", + "string", + "bool?", + "double?", + "decimal?", + "int?", + "long?", + "float?", + "byte[]", + "ICollection", + "Collection", + "List", + "Dictionary", + "DateTime?", + "DateTimeOffset?", + "String", + "Boolean", + "Double", + "Int32", + "Int64", + "Float", + "Guid?", + "System.IO.Stream", // not really a primitive, we include it to avoid model import + "Object") + ); + + instantiationTypes.put("array", "List"); + instantiationTypes.put("list", "List"); + instantiationTypes.put("map", "Dictionary"); + + // Nullable types here assume C# 2 support is not part of base + typeMapping = new HashMap(); + typeMapping.put("string", "string"); + typeMapping.put("binary", "byte[]"); + typeMapping.put("bytearray", "byte[]"); + typeMapping.put("boolean", "bool?"); + typeMapping.put("integer", "int?"); + typeMapping.put("float", "float?"); + typeMapping.put("long", "long?"); + typeMapping.put("double", "double?"); + typeMapping.put("number", "decimal?"); + typeMapping.put("datetime", "DateTime?"); + typeMapping.put("date", "DateTime?"); + typeMapping.put("file", "System.IO.Stream"); + typeMapping.put("array", "List"); + typeMapping.put("list", "List"); + typeMapping.put("map", "Dictionary"); + typeMapping.put("object", "Object"); + typeMapping.put("uuid", "Guid?"); + } + + public void setReturnICollection(boolean returnICollection) { + this.returnICollection = returnICollection; + } + + public void setOptionalEmitDefaultValue(boolean optionalEmitDefaultValue) { + this.optionalEmitDefaultValue = optionalEmitDefaultValue; + } + + public void setUseCollection(boolean useCollection) { + this.useCollection = useCollection; + if (useCollection) { + typeMapping.put("array", "Collection"); + typeMapping.put("list", "Collection"); + + instantiationTypes.put("array", "Collection"); + instantiationTypes.put("list", "Collection"); + } + } + + public void setOptionalMethodArgumentFlag(boolean flag) { + this.optionalMethodArgumentFlag = flag; + } + + public void setNetCoreProjectFileFlag(boolean flag) { + this.netCoreProjectFileFlag = flag; + } + + public void useDateTimeOffset(boolean flag) { + this.useDateTimeOffsetFlag = flag; + if (flag) typeMapping.put("datetime", "DateTimeOffset?"); + else typeMapping.put("datetime", "DateTime?"); + } + + @Override + public void processOpts() { + super.processOpts(); + + // {{packageVersion}} + if (additionalProperties.containsKey(CodegenConstants.PACKAGE_VERSION)) { + setPackageVersion((String) additionalProperties.get(CodegenConstants.PACKAGE_VERSION)); + } else { + additionalProperties.put(CodegenConstants.PACKAGE_VERSION, packageVersion); + } + + // {{sourceFolder}} + if (additionalProperties.containsKey(CodegenConstants.SOURCE_FOLDER)) { + setSourceFolder((String) additionalProperties.get(CodegenConstants.SOURCE_FOLDER)); + } else { + additionalProperties.put(CodegenConstants.SOURCE_FOLDER, this.sourceFolder); + } + + // {{packageName}} + if (additionalProperties.containsKey(CodegenConstants.PACKAGE_NAME)) { + setPackageName((String) additionalProperties.get(CodegenConstants.PACKAGE_NAME)); + } else { + additionalProperties.put(CodegenConstants.PACKAGE_NAME, packageName); + } + + if (additionalProperties.containsKey(CodegenConstants.INVOKER_PACKAGE)) { + LOGGER.warn(String.format("%s is not used by C# generators. Please use %s", CodegenConstants.INVOKER_PACKAGE, CodegenConstants.PACKAGE_NAME)); + } + + // {{packageTitle}} + if (additionalProperties.containsKey(CodegenConstants.PACKAGE_TITLE)) { + setPackageTitle((String) additionalProperties.get(CodegenConstants.PACKAGE_TITLE)); + } else { + additionalProperties.put(CodegenConstants.PACKAGE_TITLE, packageTitle); + } + + // {{packageProductName}} + if (additionalProperties.containsKey(CodegenConstants.PACKAGE_PRODUCTNAME)) { + setPackageProductName((String) additionalProperties.get(CodegenConstants.PACKAGE_PRODUCTNAME)); + } else { + additionalProperties.put(CodegenConstants.PACKAGE_PRODUCTNAME, packageProductName); + } + + // {{packageDescription}} + if (additionalProperties.containsKey(CodegenConstants.PACKAGE_DESCRIPTION)) { + setPackageDescription((String) additionalProperties.get(CodegenConstants.PACKAGE_DESCRIPTION)); + } else { + additionalProperties.put(CodegenConstants.PACKAGE_DESCRIPTION, packageDescription); + } + + // {{packageCompany}} + if (additionalProperties.containsKey(CodegenConstants.PACKAGE_COMPANY)) { + setPackageCompany((String) additionalProperties.get(CodegenConstants.PACKAGE_COMPANY)); + } else { + additionalProperties.put(CodegenConstants.PACKAGE_COMPANY, packageCompany); + } + + // {{packageCopyright}} + if (additionalProperties.containsKey(CodegenConstants.PACKAGE_COPYRIGHT)) { + setPackageCopyright((String) additionalProperties.get(CodegenConstants.PACKAGE_COPYRIGHT)); + } else { + additionalProperties.put(CodegenConstants.PACKAGE_COPYRIGHT, packageCopyright); + } + + // {{packageAuthors}} + if (additionalProperties.containsKey(CodegenConstants.PACKAGE_AUTHORS)) { + setPackageAuthors((String) additionalProperties.get(CodegenConstants.PACKAGE_AUTHORS)); + } else { + additionalProperties.put(CodegenConstants.PACKAGE_AUTHORS, packageAuthors); + } + + // {{useDateTimeOffset}} + if (additionalProperties.containsKey(CodegenConstants.USE_DATETIME_OFFSET)) { + useDateTimeOffset(convertPropertyToBooleanAndWriteBack(CodegenConstants.USE_DATETIME_OFFSET)); + } else { + additionalProperties.put(CodegenConstants.USE_DATETIME_OFFSET, useDateTimeOffsetFlag); + } + + if (additionalProperties.containsKey(CodegenConstants.USE_COLLECTION)) { + setUseCollection(convertPropertyToBooleanAndWriteBack(CodegenConstants.USE_COLLECTION)); + } else { + additionalProperties.put(CodegenConstants.USE_COLLECTION, useCollection); + } + + if (additionalProperties.containsKey(CodegenConstants.RETURN_ICOLLECTION)) { + setReturnICollection(convertPropertyToBooleanAndWriteBack(CodegenConstants.RETURN_ICOLLECTION)); + } else { + additionalProperties.put(CodegenConstants.RETURN_ICOLLECTION, returnICollection); + } + + if (additionalProperties.containsKey(CodegenConstants.OPTIONAL_EMIT_DEFAULT_VALUES)) { + setOptionalEmitDefaultValue(convertPropertyToBooleanAndWriteBack(CodegenConstants.OPTIONAL_EMIT_DEFAULT_VALUES)); + } else { + additionalProperties.put(CodegenConstants.OPTIONAL_EMIT_DEFAULT_VALUES, optionalEmitDefaultValue); + } + + if (additionalProperties.containsKey(CodegenConstants.NETCORE_PROJECT_FILE)) { + setNetCoreProjectFileFlag(convertPropertyToBooleanAndWriteBack(CodegenConstants.NETCORE_PROJECT_FILE)); + } else { + additionalProperties.put(CodegenConstants.NETCORE_PROJECT_FILE, netCoreProjectFileFlag); + } + + if (additionalProperties.containsKey(CodegenConstants.INTERFACE_PREFIX)) { + String useInterfacePrefix = additionalProperties.get(CodegenConstants.INTERFACE_PREFIX).toString(); + if("false".equals(useInterfacePrefix.toLowerCase())) { + setInterfacePrefix(""); + } else if(!"true".equals(useInterfacePrefix.toLowerCase())) { + // NOTE: if user passes "true" explicitly, we use the default I- prefix. The other supported case here is a custom prefix. + setInterfacePrefix(sanitizeName(useInterfacePrefix)); + } + } + + // This either updates additionalProperties with the above fixes, or sets the default if the option was not specified. + additionalProperties.put(CodegenConstants.INTERFACE_PREFIX, interfacePrefix); + + addMustacheLambdas(additionalProperties); + } + + private void addMustacheLambdas(Map objs) { + + Map lambdas = new ImmutableMap.Builder() + .put("lowercase", new LowercaseLambda().generator(this)) + .put("uppercase", new UppercaseLambda()) + .put("titlecase", new TitlecaseLambda()) + .put("camelcase", new CamelCaseLambda().generator(this)) + .put("camelcase_param", new CamelCaseLambda().generator(this).escapeAsParamName(true)) + .put("indented", new IndentedLambda()) + .put("indented_8", new IndentedLambda(8, " ")) + .put("indented_12", new IndentedLambda(12, " ")) + .put("indented_16", new IndentedLambda(16, " ")) + .build(); + + if (objs.containsKey("lambda")) { + LOGGER.warn("An property named 'lambda' already exists. Mustache lambdas renamed from 'lambda' to '_lambda'. " + + "You'll likely need to use a custom template, " + + "see https://github.com/swagger-api/swagger-codegen#modifying-the-client-library-format. "); + objs.put("_lambda", lambdas); + } else { + objs.put("lambda", lambdas); + } + } + + @Override + public void postProcessModelProperty(CodegenModel model, CodegenProperty property) { + super.postProcessModelProperty(model, property); + } + + @Override + public Map postProcessModels(Map objs) { + List models = (List) objs.get("models"); + for (Object _mo : models) { + Map mo = (Map) _mo; + CodegenModel cm = (CodegenModel) mo.get("model"); + for (CodegenProperty var : cm.vars) { + // check to see if model name is same as the property name + // which will result in compilation error + // if found, prepend with _ to workaround the limitation + if (var.name.equalsIgnoreCase(cm.name)) { + var.name = "_" + var.name; + } + } + } + // process enum in models + return postProcessModelsEnum(objs); + } + + /** + * Invoked by {@link DefaultGenerator} after all models have been post-processed, allowing for a last pass of codegen-specific model cleanup. + * + * @param objs Current state of codegen object model. + * @return An in-place modified state of the codegen object model. + */ + @Override + public Map postProcessAllModels(Map objs) { + final Map processed = super.postProcessAllModels(objs); + postProcessEnumRefs(processed); + return processed; + } + + /** + * C# differs from other languages in that Enums are not _true_ objects; enums are compiled to integral types. + * So, in C#, an enum is considers more like a user-defined primitive. + * + * When working with enums, we can't always assume a RefModel is a nullable type (where default(YourType) == null), + * so this post processing runs through all models to find RefModel'd enums. Then, it runs through all vars and modifies + * those vars referencing RefModel'd enums to work the same as inlined enums rather than as objects. + * @param models processed models to be further processed for enum references + */ + @SuppressWarnings({ "unchecked" }) + private void postProcessEnumRefs(final Map models) { + Map enumRefs = new HashMap(); + for (Map.Entry entry : models.entrySet()) { + CodegenModel model = ModelUtils.getModelByName(entry.getKey(), models); + if (model.isEnum) { + enumRefs.put(entry.getKey(), model); + } + } + + for (Map.Entry entry : models.entrySet()) { + String swaggerName = entry.getKey(); + CodegenModel model = ModelUtils.getModelByName(swaggerName, models); + if (model != null) { + for (CodegenProperty var : model.allVars) { + if (enumRefs.containsKey(var.datatype)) { + // Handle any enum properties referred to by $ref. + // This is different in C# than most other generators, because enums in C# are compiled to integral types, + // while enums in many other languages are true objects. + CodegenModel refModel = enumRefs.get(var.datatype); + var.allowableValues = refModel.allowableValues; + var.isEnum = true; + + updateCodegenPropertyEnum(var); + + // We do these after updateCodegenPropertyEnum to avoid generalities that don't mesh with C#. + var.isPrimitiveType = true; + } + } + + // We're looping all models here. + if (model.isEnum) { + // We now need to make allowableValues.enumVars look like the context of CodegenProperty + Boolean isString = false; + Boolean isInteger = false; + Boolean isLong = false; + Boolean isByte = false; + + if (model.dataType.startsWith("byte")) { + // C# Actually supports byte and short enums, swagger spec only supports byte. + isByte = true; + model.vendorExtensions.put("x-enum-byte", true); + } else if (model.dataType.startsWith("int32")) { + isInteger = true; + model.vendorExtensions.put("x-enum-integer", true); + } else if (model.dataType.startsWith("int64")) { + isLong = true; + model.vendorExtensions.put("x-enum-long", true); + } else { + // C# doesn't support non-integral enums, so we need to treat everything else as strings (e.g. to not lose precision or data integrity) + isString = true; + model.vendorExtensions.put("x-enum-string", true); + } + + // Since we iterate enumVars for modelnnerEnum and enumClass templates, and CodegenModel is missing some of CodegenProperty's properties, + // we can take advantage of Mustache's contextual lookup to add the same "properties" to the model's enumVars scope rather than CodegenProperty's scope. + List> enumVars = (ArrayList>)model.allowableValues.get("enumVars"); + List> newEnumVars = new ArrayList>(); + for (Map enumVar : enumVars) { + Map mixedVars = new HashMap(); + mixedVars.putAll(enumVar); + + mixedVars.put("isString", isString); + mixedVars.put("isLong", isLong); + mixedVars.put("isInteger", isInteger); + mixedVars.put("isByte", isByte); + + newEnumVars.add(mixedVars); + } + + if (!newEnumVars.isEmpty()) { + model.allowableValues.put("enumVars", newEnumVars); + } + } + } else { + LOGGER.warn("Expected to retrieve model %s by name, but no model was found. Check your -Dmodels inclusions.", swaggerName); + } + } + } + + /** + * Update codegen property's enum by adding "enumVars" (with name and value) + * + * @param var list of CodegenProperty + */ + @Override + public void updateCodegenPropertyEnum(CodegenProperty var) { + if (var.vendorExtensions == null) { + var.vendorExtensions = new HashMap<>(); + } + + super.updateCodegenPropertyEnum(var); + + // Because C# uses nullable primitives for datatype, and datatype is used in DefaultCodegen for determining enum-ness, guard against weirdness here. + if (var.isEnum) { + if ("byte".equals(var.dataFormat)) {// C# Actually supports byte and short enums. + var.vendorExtensions.put("x-enum-byte", true); + var.isString = false; + var.isLong = false; + var.isInteger = false; + } else if ("int32".equals(var.dataFormat)) { + var.isInteger = true; + var.isString = false; + var.isLong = false; + } else if ("int64".equals(var.dataFormat)) { + var.isLong = true; + var.isString = false; + var.isInteger = false; + } else {// C# doesn't support non-integral enums, so we need to treat everything else as strings (e.g. to not lose precision or data integrity) + var.isString = true; + var.isInteger = false; + var.isLong = false; + } + } + } + + @Override + public Map postProcessOperations(Map objs) { + super.postProcessOperations(objs); + if (objs != null) { + Map operations = (Map) objs.get("operations"); + if (operations != null) { + List ops = (List) operations.get("operation"); + for (CodegenOperation operation : ops) { + + // Check return types for collection + if (operation.returnType != null) { + String typeMapping; + int namespaceEnd = operation.returnType.lastIndexOf("."); + if (namespaceEnd > 0) { + typeMapping = operation.returnType.substring(namespaceEnd); + } else { + typeMapping = operation.returnType; + } + + if (this.collectionTypes.contains(typeMapping)) { + operation.isListContainer = true; + operation.returnContainer = operation.returnType; + if (this.returnICollection && ( + typeMapping.startsWith("List") || + typeMapping.startsWith("Collection"))) { + // NOTE: ICollection works for both List and Collection + int genericStart = typeMapping.indexOf("<"); + if (genericStart > 0) { + operation.returnType = "ICollection" + typeMapping.substring(genericStart); + } + } + } else { + operation.returnContainer = operation.returnType; + operation.isMapContainer = this.mapTypes.contains(typeMapping); + } + } + + if (operation.examples != null){ + for (Map example : operation.examples) + { + for (Map.Entry entry : example.entrySet()) + { + // Replace " with \", \r, \n with \\r, \\n + String val = entry.getValue().replace("\"", "\\\"") + .replace("\r","\\r") + .replace("\n","\\n"); + entry.setValue(val); + } + } + } + + processOperation(operation); + } + } + } + + return objs; + } + + protected void processOperation(CodegenOperation operation) { + // default noop + } + + @Override + public String apiFileFolder() { + return outputFolder + File.separator + sourceFolder + File.separator + packageName + File.separator + apiPackage(); + } + + @Override + public String modelFileFolder() { + return outputFolder + File.separator + sourceFolder + File.separator + packageName + File.separator + modelPackage(); + } + + @Override + public String toModelFilename(String name) { + // should be the same as the model name + return toModelName(name); + } + + @Override + public String toOperationId(String operationId) { + // throw exception if method name is empty (should not occur as an auto-generated method name will be used) + 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)) { + LOGGER.warn(operationId + " (reserved word) cannot be used as method name. Renamed to " + camelize(sanitizeName("call_" + operationId))); + operationId = "call_" + operationId; + } + + return camelize(sanitizeName(operationId)); + } + + @Override + public String toVarName(String name) { + // sanitize name + name = sanitizeName(name); + + // if it's all uppper case, do nothing + if (name.matches("^[A-Z_]*$")) { + return name; + } + + // camelize the variable name + // pet_id => PetId + name = camelize(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) { + // sanitize name + name = sanitizeName(name); + + // replace - with _ e.g. created-at => created_at + name = name.replaceAll("-", "_"); + + // if it's all uppper case, do nothing + if (name.matches("^[A-Z_]*$")) { + return name; + } + + // camelize(lower) the variable name + // pet_id => petId + name = camelize(name, true); + + // for reserved word or word starting with number, append _ + if (isReservedWord(name) || name.matches("^\\d.*")) { + name = escapeReservedWord(name); + } + + return name; + } + + @Override + public String escapeReservedWord(String name) { + if(this.reservedWordsMappings().containsKey(name)) { + return this.reservedWordsMappings().get(name); + } + return "_" + name; + } + + /** + * Return the example value of the property + * + * @param p Swagger property object + * @return string presentation of the example value of the property + */ + @Override + public String toExampleValue(Schema p) { + if (p instanceof StringSchema) { + StringSchema dp = (StringSchema) p; + if (dp.getExample() != null) { + return "\"" + dp.getExample().toString() + "\""; + } + } else if (p instanceof BooleanSchema) { + BooleanSchema dp = (BooleanSchema) p; + if (dp.getExample() != null) { + return dp.getExample().toString(); + } + } else if (p instanceof DateSchema) { + // TODO + } else if (p instanceof DateTimeSchema) { + // TODO + } else if (p instanceof NumberSchema) { + NumberSchema dp = (NumberSchema) p; + if (dp.getExample() != null) { + return dp.getExample().toString(); + } + } else if (p instanceof IntegerSchema) { + IntegerSchema dp = (IntegerSchema) p; + if (dp.getExample() != null) { + return dp.getExample().toString(); + } + } + + return null; + } + + /** + * Return the default value of the property + * + * @param p Swagger property object + * @return string presentation of the default value of the property + */ + @Override + public String toDefaultValue(Schema p) { + if (p instanceof StringSchema) { + StringSchema dp = (StringSchema) p; + if (dp.getDefault() != null) { + String _default = dp.getDefault(); + if (dp.getEnum() == null) { + return "\"" + _default + "\""; + } else { + // convert to enum var name later in postProcessModels + return _default; + } + } + } else if (p instanceof BooleanSchema) { + BooleanSchema dp = (BooleanSchema) p; + if (dp.getDefault() != null) { + return dp.getDefault().toString(); + } + } else if (p instanceof DateSchema) { + // TODO + } else if (p instanceof DateTimeSchema) { + // TODO + } else if (p instanceof NumberSchema) { + NumberSchema dp = (NumberSchema) p; + if (dp.getDefault() != null) { + return dp.getDefault().toString(); + } + } else if (p instanceof IntegerSchema) { + IntegerSchema dp = (IntegerSchema) p; + if (dp.getDefault() != null) { + return dp.getDefault().toString(); + } + } + + return null; + } + + @Override + protected boolean isReservedWord(String word) { + // NOTE: This differs from super's implementation in that C# does _not_ want case insensitive matching. + return reservedWords.contains(word); + } + + @Override + public String getSchemaType(Schema p) { + String openAPIType = super.getSchemaType(p); + String type; + + if (openAPIType == null) { + openAPIType = ""; // set swagger type to empty string if null + } + + // NOTE: typeMapping here supports things like string/String, long/Long, datetime/DateTime as lowercase keys. + // Should we require explicit casing here (values are not insensitive). + // TODO avoid using toLowerCase as typeMapping should be case-sensitive + if (typeMapping.containsKey(openAPIType.toLowerCase())) { + type = typeMapping.get(openAPIType.toLowerCase()); + if (languageSpecificPrimitives.contains(type)) { + return type; + } + } else { + type = openAPIType; + } + return toModelName(type); + } + + /** + * Provides C# strongly typed declaration for simple arrays of some type and arrays of arrays of some type. + * @param arr The input array property + * @return The type declaration when the type is an array of arrays. + */ + private String getArrayTypeDeclaration(ArraySchema arr) { + // TODO: collection type here should be fully qualified namespace to avoid model conflicts + // This supports arrays of arrays. + String arrayType = typeMapping.get("array"); + StringBuilder instantiationType = new StringBuilder(arrayType); + Schema items = arr.getItems(); + String nestedType = getTypeDeclaration(items); + // TODO: We may want to differentiate here between generics and primitive arrays. + instantiationType.append("<").append(nestedType).append(">"); + return instantiationType.toString(); + } + + @Override + public String toInstantiationType(Schema p) { + if (p instanceof ArraySchema) { + return getArrayTypeDeclaration((ArraySchema) p); + } + return super.toInstantiationType(p); + } + + @Override + public String getTypeDeclaration(Schema p) { + if (p instanceof ArraySchema) { + return getArrayTypeDeclaration((ArraySchema) p); + } else if (p instanceof MapSchema) { + // Should we also support maps of maps? + MapSchema mp = (MapSchema) p; + Schema inner = (Schema) mp.getAdditionalProperties(); + return getSchemaType(p) + ""; + } + return super.getTypeDeclaration(p); + } + + @Override + public String toModelName(String name) { + // We need to check if import-mapping has a different model for this class, so we use it + // instead of the auto-generated one. + if (importMapping.containsKey(name)) { + return importMapping.get(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 " + camelize("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 " + camelize("model_" + name)); + name = "model_" + name; // e.g. 200Response => Model200Response (after camelize) + } + + // camelize the model name + // phone_number => PhoneNumber + return camelize(name); + } + + @Override + public String apiTestFileFolder() { + return outputFolder + ".Test"; + } + + @Override + public String modelTestFileFolder() { + return outputFolder + ".Test"; + } + + @Override + public String toApiTestFilename(String name) { + return toApiName(name) + "Tests"; + } + + @Override + public String toModelTestFilename(String name) { + return toModelName(name) + "Tests"; + } + + public void setPackageName(String packageName) { + this.packageName = packageName; + } + + public void setPackageVersion(String packageVersion) { + this.packageVersion = packageVersion; + } + + public void setPackageTitle(String packageTitle) { + this.packageTitle = packageTitle; + } + + public void setPackageProductName(String packageProductName) { + this.packageProductName = packageProductName; + } + + public void setPackageDescription(String packageDescription) { + this.packageDescription = packageDescription; + } + + public void setPackageCompany(String packageCompany) { + this.packageCompany = packageCompany; + } + + public void setPackageCopyright(String packageCopyright) { + this.packageCopyright = packageCopyright; + } + + public void setPackageAuthors(String packageAuthors) { + this.packageAuthors = packageAuthors; + } + + public void setSourceFolder(String sourceFolder) { + this.sourceFolder = sourceFolder; + } + + public String getInterfacePrefix() { + return interfacePrefix; + } + + public void setInterfacePrefix(final String interfacePrefix) { + this.interfacePrefix = interfacePrefix; + } + + @Override + public String toEnumValue(String value, String datatype) { + // C# only supports enums as literals for int, int?, long, long?, byte, and byte?. All else must be treated as strings. + // Per: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/enum + // The approved types for an enum are byte, sbyte, short, ushort, int, uint, long, or ulong. + // but we're not supporting unsigned integral types or shorts. + if(datatype.startsWith("int") || datatype.startsWith("long") || datatype.startsWith("byte")) { + return value; + } + + return escapeText(value); + } + + @Override + public String toEnumVarName(String name, String datatype) { + if (name.length() == 0) { + return "Empty"; + } + + // for symbol, e.g. $, # + if (getSymbolName(name) != null) { + return camelize(getSymbolName(name)); + } + + String enumName = sanitizeName(name); + + enumName = enumName.replaceFirst("^_", ""); + enumName = enumName.replaceFirst("_$", ""); + + enumName = camelize(enumName) + "Enum"; + + if (enumName.matches("\\d.*")) { // starts with number + return "_" + enumName; + } else { + return enumName; + } + } + + @Override + public String toEnumName(CodegenProperty property) { + return sanitizeName(camelize(property.name)) + "Enum"; + } + + public String testPackageName() { + return this.packageName + ".Test"; + } + + @Override + public String escapeQuotationMark(String input) { + // remove " to avoid code injection + return input.replace("\"", ""); + } + + @Override + public String escapeUnsafeCharacters(String input) { + return input.replace("*/", "*_/").replace("/*", "/_*").replace("--", "- -"); + } +} diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppRestbedServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppRestbedServerCodegen.java index e9e798852b0..55f1695413f 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppRestbedServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppRestbedServerCodegen.java @@ -90,7 +90,6 @@ public class CppRestbedServerCodegen extends AbstractCppCodegen { * Configures the type of generator. * * @return the CodegenType for this generator - * @see io.swagger.codegen.CodegenType */ public CodegenType getTag() { return CodegenType.SERVER;