diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractPhpCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractPhpCodegen.java new file mode 100644 index 00000000000..c9ac464189b --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractPhpCodegen.java @@ -0,0 +1,664 @@ +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.CodegenParameter; +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 java.io.File; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.HashSet; +import java.util.regex.Matcher; + +import org.apache.commons.lang3.StringUtils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class AbstractPhpCodegen extends DefaultCodegen implements CodegenConfig { + + static Logger LOGGER = LoggerFactory.getLogger(AbstractPhpCodegen.class); + + public static final String VARIABLE_NAMING_CONVENTION = "variableNamingConvention"; + public static final String PACKAGE_PATH = "packagePath"; + public static final String SRC_BASE_PATH = "srcBasePath"; + // composerVendorName/composerProjectName has be replaced by gitUserId/gitRepoId. prepare to remove these. + // public static final String COMPOSER_VENDOR_NAME = "composerVendorName"; + // public static final String COMPOSER_PROJECT_NAME = "composerProjectName"; + // protected String composerVendorName = null; + // protected String composerProjectName = null; + protected String invokerPackage = "php"; + protected String packagePath = "php-base"; + protected String artifactVersion = null; + protected String srcBasePath = "lib"; + protected String testBasePath = "test"; + protected String docsBasePath = "docs"; + protected String apiDirName = "Api"; + protected String modelDirName = "Model"; + protected String variableNamingConvention= "snake_case"; + protected String apiDocPath = docsBasePath + File.separator + apiDirName; + protected String modelDocPath = docsBasePath + File.separator + modelDirName; + + public AbstractPhpCodegen() { + super(); + + modelTemplateFiles.put("model.mustache", ".php"); + apiTemplateFiles.put("api.mustache", ".php"); + apiTestTemplateFiles.put("api_test.mustache", ".php"); + modelDocTemplateFiles.put("model_doc.mustache", ".md"); + apiDocTemplateFiles.put("api_doc.mustache", ".md"); + + apiPackage = invokerPackage + "\\" + apiDirName; + modelPackage = invokerPackage + "\\" + modelDirName; + + 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", + "boolean", + "int", + "integer", + "double", + "float", + "string", + "object", + "DateTime", + "mixed", + "number", + "void", + "byte") + ); + + instantiationTypes.put("array", "array"); + instantiationTypes.put("map", "map"); + + + // provide primitives to mustache template + String primitives = "'" + StringUtils.join(languageSpecificPrimitives, "', '") + "'"; + 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", "\\SplFileObject"); + typeMapping.put("map", "map"); + typeMapping.put("array", "array"); + typeMapping.put("list", "array"); + typeMapping.put("object", "object"); + typeMapping.put("binary", "string"); + typeMapping.put("ByteArray", "string"); + typeMapping.put("UUID", "string"); + + 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(VARIABLE_NAMING_CONVENTION, "naming convention of variable name, e.g. camelCase.") + .defaultValue("snake_case")); + cliOptions.add(new CliOption(CodegenConstants.INVOKER_PACKAGE, "The main namespace to use for all classes. e.g. Yay\\Pets")); + cliOptions.add(new CliOption(PACKAGE_PATH, "The main package name for classes. e.g. GeneratedPetstore")); + cliOptions.add(new CliOption(SRC_BASE_PATH, "The directory under packagePath to serve as source root.")); + // 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(CodegenConstants.GIT_USER_ID, CodegenConstants.GIT_USER_ID_DESC)); + // 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.GIT_REPO_ID, CodegenConstants.GIT_REPO_ID_DESC)); + cliOptions.add(new CliOption(CodegenConstants.ARTIFACT_VERSION, "The version to use in the composer package version field. e.g. 1.2.3")); + } + + @Override + public void processOpts() { + super.processOpts(); + + if (additionalProperties.containsKey(PACKAGE_PATH)) { + this.setPackagePath((String) additionalProperties.get(PACKAGE_PATH)); + } else { + additionalProperties.put(PACKAGE_PATH, packagePath); + } + + if (additionalProperties.containsKey(SRC_BASE_PATH)) { + this.setSrcBasePath((String) additionalProperties.get(SRC_BASE_PATH)); + } else { + additionalProperties.put(SRC_BASE_PATH, srcBasePath); + } + + if (additionalProperties.containsKey(CodegenConstants.INVOKER_PACKAGE)) { + this.setInvokerPackage((String) additionalProperties.get(CodegenConstants.INVOKER_PACKAGE)); + } else { + additionalProperties.put(CodegenConstants.INVOKER_PACKAGE, invokerPackage); + } + + if (!additionalProperties.containsKey(CodegenConstants.MODEL_PACKAGE)) { + additionalProperties.put(CodegenConstants.MODEL_PACKAGE, modelPackage); + } + + if (!additionalProperties.containsKey(CodegenConstants.API_PACKAGE)) { + additionalProperties.put(CodegenConstants.API_PACKAGE, apiPackage); + } + + // if (additionalProperties.containsKey(COMPOSER_PROJECT_NAME)) { + // this.setComposerProjectName((String) additionalProperties.get(COMPOSER_PROJECT_NAME)); + // } else { + // additionalProperties.put(COMPOSER_PROJECT_NAME, composerProjectName); + // } + + if (additionalProperties.containsKey(CodegenConstants.GIT_USER_ID)) { + this.setGitUserId((String) additionalProperties.get(CodegenConstants.GIT_USER_ID)); + } else { + additionalProperties.put(CodegenConstants.GIT_USER_ID, gitUserId); + } + + // if (additionalProperties.containsKey(COMPOSER_VENDOR_NAME)) { + // this.setComposerVendorName((String) additionalProperties.get(COMPOSER_VENDOR_NAME)); + // } else { + // additionalProperties.put(COMPOSER_VENDOR_NAME, composerVendorName); + // } + + if (additionalProperties.containsKey(CodegenConstants.GIT_REPO_ID)) { + this.setGitRepoId((String) additionalProperties.get(CodegenConstants.GIT_REPO_ID)); + } else { + additionalProperties.put(CodegenConstants.GIT_REPO_ID, gitRepoId); + } + + if (additionalProperties.containsKey(CodegenConstants.ARTIFACT_VERSION)) { + this.setArtifactVersion((String) additionalProperties.get(CodegenConstants.ARTIFACT_VERSION)); + } else { + additionalProperties.put(CodegenConstants.ARTIFACT_VERSION, artifactVersion); + } + + if (additionalProperties.containsKey(VARIABLE_NAMING_CONVENTION)) { + this.setParameterNamingConvention((String) additionalProperties.get(VARIABLE_NAMING_CONVENTION)); + } + + additionalProperties.put("escapedInvokerPackage", invokerPackage.replace("\\", "\\\\")); + + // 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("apiTestPath", "." + File.separator + testBasePath + File.separator + apiDirName); + additionalProperties.put("modelTestPath", "." + File.separator + testBasePath + 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("testBasePath", testBasePath); + + // // apache v2 license + // supportingFiles.add(new SupportingFile("LICENSE", getPackagePath(), "LICENSE")); + } + + public String getPackagePath() { + return packagePath; + } + + public String toPackagePath(String packageName, String basePath) { + return (getPackagePath() + File.separatorChar + toSrcPath(packageName, basePath)); + } + + public String toSrcPath(String packageName, String basePath) { + packageName = packageName.replace(invokerPackage, ""); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. + if (basePath != null && basePath.length() > 0) { + basePath = basePath.replaceAll("[\\\\/]?$", "") + File.separatorChar; // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. + } + + String regFirstPathSeparator; + if ("/".equals(File.separator)) { // for mac, linux + regFirstPathSeparator = "^/"; + } else { // for windows + regFirstPathSeparator = "^\\\\"; + } + + String regLastPathSeparator; + if ("/".equals(File.separator)) { // for mac, linux + regLastPathSeparator = "/$"; + } else { // for windows + regLastPathSeparator = "\\\\$"; + } + + return (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+ "$", ""); + } + + @Override + public String escapeReservedWord(String name) { + if(this.reservedWordsMappings().containsKey(name)) { + return this.reservedWordsMappings().get(name); + } + return "_" + name; + } + + @Override + public String apiFileFolder() { + return (outputFolder + File.separator + toPackagePath(apiPackage, srcBasePath)); + } + + @Override + public String modelFileFolder() { + return (outputFolder + File.separator + toPackagePath(modelPackage, srcBasePath)); + } + + @Override + public String apiTestFileFolder() { + return (outputFolder + File.separator + getPackagePath() + File.separator + testBasePath + File.separator + apiDirName); + } + + @Override + public String modelTestFileFolder() { + return (outputFolder + File.separator + getPackagePath() + File.separator + testBasePath + File.separator + modelDirName); + } + + @Override + public String apiDocFileFolder() { + return (outputFolder + File.separator + getPackagePath() + File.separator + apiDocPath); + } + + @Override + public String modelDocFileFolder() { + return (outputFolder + File.separator + getPackagePath() + File.separator + modelDocPath); + } + + @Override + public String toModelDocFilename(String name) { + return toModelName(name); + } + + @Override + public String toApiDocFilename(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 getSchemaType(p) + "[string," + getTypeDeclaration(inner) + "]"; + } else if (!StringUtils.isEmpty(p.get$ref())) { // model + String type = super.getTypeDeclaration(p); + return (!languageSpecificPrimitives.contains(type)) + ? "\\" + modelPackage + "\\" + type : type; + } + return super.getTypeDeclaration(p); + } + + @Override + public String getTypeDeclaration(String name) { + if (!languageSpecificPrimitives.contains(name)) { + return "\\" + modelPackage + "\\" + name; + } + return super.getTypeDeclaration(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 type; + } else if (instantiationTypes.containsKey(type)) { + return type; + } + } else { + type = openAPIType; + } + if (type == null) { + return null; + } + return toModelName(type); + } + + public void setInvokerPackage(String invokerPackage) { + this.invokerPackage = invokerPackage; + } + + public void setArtifactVersion(String artifactVersion) { + this.artifactVersion = artifactVersion; + } + + public void setPackagePath(String packagePath) { + this.packagePath = packagePath; + } + + public void setSrcBasePath(String srcBasePath) { + this.srcBasePath = srcBasePath; + } + + public void setParameterNamingConvention(String variableNamingConvention) { + this.variableNamingConvention = variableNamingConvention; + } + + // public void setComposerVendorName(String composerVendorName) { + // this.composerVendorName = composerVendorName; + // } + + // public void setComposerProjectName(String composerProjectName) { + // this.composerProjectName = composerProjectName; + // } + + @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 ("camelCase".equals(variableNamingConvention)) { + // return the name in camelCase style + // phone_number => phoneNumber + name = camelize(name, true); + } else { // default to snake case + // return the name in underscore style + // PhoneNumber => phone_number + name = underscore(name); + } + + // 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) { + // remove [ + name = name.replaceAll("\\]", ""); + + // Note: backslash ("\\") is allowed for e.g. "\\DateTime" + name = name.replaceAll("[^\\w\\\\]+", "_"); // 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 + 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) + } + + // add prefix and/or suffic only if name does not start wth \ (e.g. \DateTime) + if (!name.matches("^\\\\.*")) { + name = modelNamePrefix + 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) { + // should be the same as the model name + return toModelName(name) + "Test"; + } + + @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)) { + LOGGER.warn(operationId + " (reserved word) cannot be used as method name. Renamed to " + camelize(sanitizeName("call_" + operationId), true)); + operationId = "call_" + operationId; + } + + return camelize(sanitizeName(operationId), true); + } + + /** + * 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) { + 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 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)) { + if (example == null) { + example = p.paramName + "_example"; + } + example = "\"" + escapeText(example) + "\""; + } else if ("Integer".equals(type) || "int".equals(type)) { + if (example == null) { + example = "56"; + } + } else if ("Float".equalsIgnoreCase(type) || "Double".equalsIgnoreCase(type)) { + if (example == null) { + example = "3.4"; + } + } else if ("BOOLEAN".equalsIgnoreCase(type) || "bool".equalsIgnoreCase(type)) { + if (example == null) { + example = "True"; + } + } else if ("\\SplFileObject".equalsIgnoreCase(type)) { + if (example == null) { + example = "/path/to/file"; + } + example = "\"" + escapeText(example) + "\""; + } else if ("Date".equalsIgnoreCase(type)) { + if (example == null) { + example = "2013-10-20"; + } + example = "new \\DateTime(\"" + escapeText(example) + "\")"; + } else if ("DateTime".equalsIgnoreCase(type)) { + if (example == null) { + example = "2013-10-20T19:20:30+01:00"; + } + example = "new \\DateTime(\"" + escapeText(example) + "\")"; + } else if (!languageSpecificPrimitives.contains(type)) { + // type is a model class, e.g. User + example = "new " + getTypeDeclaration(type) + "()"; + } else { + LOGGER.warn("Type " + type + " not handled properly in setParameterExampleValue"); + } + + if (example == null) { + example = "NULL"; + } else if (Boolean.TRUE.equals(p.isListContainer)) { + example = "array(" + example + ")"; + } else if (Boolean.TRUE.equals(p.isMapContainer)) { + example = "array('key' => " + example + ")"; + } + + p.example = example; + } + + @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"; + } + + // for symbol, e.g. $, # + if (getSymbolName(name) != null) { + return (getSymbolName(name)).toUpperCase(); + } + + // 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; + } + + // string + String enumName = sanitizeName(underscore(name).toUpperCase()); + enumName = enumName.replaceFirst("^_", ""); + enumName = enumName.replaceFirst("_$", ""); + + if (enumName.matches("\\d.*")) { // starts with number + return "_" + enumName; + } else { + return enumName; + } + } + + @Override + public String toEnumName(CodegenProperty property) { + String enumName = underscore(toModelName(property.name)).toUpperCase(); + + if (enumName.matches("\\d.*")) { // starts with number + return "_" + enumName; + } else { + return enumName; + } + } + + @Override + public Map postProcessModels(Map objs) { + // process enum in models + return postProcessModelsEnum(objs); + } + + @Override + public Map postProcessOperations(Map objs) { + Map operations = (Map) objs.get("operations"); + List operationList = (List) operations.get("operation"); + for (CodegenOperation op : operationList) { + op.vendorExtensions.put("x-testOperationId", camelize(op.operationId)); + } + return objs; + } + + @Override + public String escapeQuotationMark(String input) { + // remove ' to avoid code injection + return input.replace("'", ""); + } + + @Override + public String escapeUnsafeCharacters(String input) { + return input.replace("*/", ""); + } + + protected String extractSimpleName(String phpClassName) { + if (phpClassName == null) { + return null; + } + + final int lastBackslashIndex = phpClassName.lastIndexOf('\\'); + return phpClassName.substring(lastBackslashIndex + 1); + } +} 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 new file mode 100644 index 00000000000..0d5d50ed3ab --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AndroidClientCodegen.java @@ -0,0 +1,595 @@ +package org.openapitools.codegen.languages; + +import org.openapitools.codegen.CliOption; +import org.openapitools.codegen.CodegenConfig; +import org.openapitools.codegen.CodegenConstants; +import org.openapitools.codegen.CodegenParameter; +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 java.io.File; +import java.util.Arrays; +import java.util.HashSet; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class AndroidClientCodegen extends DefaultCodegen implements CodegenConfig { + private static final Logger LOGGER = LoggerFactory.getLogger(AndroidClientCodegen.class); + public static final String USE_ANDROID_MAVEN_GRADLE_PLUGIN = "useAndroidMavenGradlePlugin"; + public static final String ANDROID_GRADLE_VERSION = "androidGradleVersion"; + public static final String ANDROID_SDK_VERSION = "androidSdkVersion"; + public static final String ANDROID_BUILD_TOOLS_VERSION = "androidBuildToolsVersion"; + protected String invokerPackage = "io.swagger.client"; + protected String groupId = "io.swagger"; + protected String artifactId = "swagger-android-client"; + protected String artifactVersion = "1.0.0"; + protected String projectFolder = "src/main"; + protected String sourceFolder = projectFolder + "/java"; + protected Boolean useAndroidMavenGradlePlugin = true; + protected String androidGradleVersion; + protected String androidSdkVersion; + protected String androidBuildToolsVersion; + protected Boolean serializableModel = false; + + // requestPackage and authPackage are used by the "volley" template/library + protected String requestPackage = "io.swagger.client.request"; + protected String authPackage = "io.swagger.client.auth"; + protected String gradleWrapperPackage = "gradle.wrapper"; + protected String apiDocPath = "docs/"; + protected String modelDocPath = "docs/"; + + public AndroidClientCodegen() { + super(); + outputFolder = "generated-code/android"; + modelTemplateFiles.put("model.mustache", ".java"); + apiTemplateFiles.put("api.mustache", ".java"); + embeddedTemplateDir = templateDir = "android"; + apiPackage = "io.swagger.client.api"; + modelPackage = "io.swagger.client.model"; + + setReservedWordsLowerCase( + Arrays.asList( + // local variable names used in API methods (endpoints) + "localVarPostBody", "localVarPath", "localVarQueryParams", "localVarHeaderParams", + "localVarFormParams", "localVarContentTypes", "localVarContentType", + "localVarResponse", "localVarBuilder", "authNames", "basePath", "apiInvoker", + + // due to namespace collusion + "Object", + + // android reserved words + "abstract", "continue", "for", "new", "switch", "assert", + "default", "if", "package", "synchronized", "boolean", "do", "goto", "private", + "this", "break", "double", "implements", "protected", "throw", "byte", "else", + "import", "public", "throws", "case", "enum", "instanceof", "return", "transient", + "catch", "extends", "int", "short", "try", "char", "final", "interface", "static", + "void", "class", "finally", "long", "strictfp", "volatile", "const", "float", + "native", "super", "while", "null") + ); + + languageSpecificPrimitives = new HashSet( + Arrays.asList( + "String", + "boolean", + "Boolean", + "Double", + "Integer", + "Long", + "Float", + "byte[]", + "Object") + ); + instantiationTypes.put("array", "ArrayList"); + instantiationTypes.put("map", "HashMap"); + typeMapping.put("date", "Date"); + typeMapping.put("file", "File"); + + 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.INVOKER_PACKAGE, CodegenConstants.INVOKER_PACKAGE_DESC)); + cliOptions.add(new CliOption(CodegenConstants.GROUP_ID, "groupId for use in the generated build.gradle and pom.xml")); + cliOptions.add(new CliOption(CodegenConstants.ARTIFACT_ID, "artifactId for use in the generated build.gradle and pom.xml")); + cliOptions.add(new CliOption(CodegenConstants.ARTIFACT_VERSION, "artifact version for use in the generated build.gradle and pom.xml")); + cliOptions.add(new CliOption(CodegenConstants.SOURCE_FOLDER, CodegenConstants.SOURCE_FOLDER_DESC)); + cliOptions.add(CliOption.newBoolean(USE_ANDROID_MAVEN_GRADLE_PLUGIN, "A flag to toggle android-maven gradle plugin.") + .defaultValue(Boolean.TRUE.toString())); + cliOptions.add(new CliOption(ANDROID_GRADLE_VERSION, "gradleVersion version for use in the generated build.gradle")); + cliOptions.add(new CliOption(ANDROID_SDK_VERSION, "compileSdkVersion version for use in the generated build.gradle")); + cliOptions.add(new CliOption(ANDROID_BUILD_TOOLS_VERSION, "buildToolsVersion version for use in the generated build.gradle")); + + cliOptions.add(CliOption.newBoolean(CodegenConstants.SERIALIZABLE_MODEL, CodegenConstants.SERIALIZABLE_MODEL_DESC)); + + supportedLibraries.put("volley", "HTTP client: Volley 1.0.19 (default)"); + supportedLibraries.put("httpclient", "HTTP client: Apache HttpClient 4.3.6. JSON processing: Gson 2.3.1. IMPORTANT: Android client using HttpClient is not actively maintained and will be depecreated in the next major release."); + CliOption library = new CliOption(CodegenConstants.LIBRARY, "library template (sub-template) to use"); + library.setEnum(supportedLibraries); + cliOptions.add(library); + } + + @Override + public CodegenType getTag() { + return CodegenType.CLIENT; + } + + @Override + public String getName() { + return "android"; + } + + @Override + public String getHelp() { + return "Generates an Android 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 + "/" + sourceFolder + "/" + apiPackage().replace('.', File.separatorChar); + } + + @Override + public String modelFileFolder() { + return outputFolder + "/" + sourceFolder + "/" + modelPackage().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 toApiDocFilename(String name) { + return toApiName(name); + } + + @Override + public String toModelDocFilename(String 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 getSchemaType(p) + ""; + } + 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) || type.indexOf(".") >= 0 || + type.equals("Map") || type.equals("List") || + type.equals("File") || type.equals("Date")) { + return type; + } + } else { + type = swaggerType; + } + return toModelName(type); + } + + @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'. + + // 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'. + + // 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) { + // add prefix, suffix if needed + if (!StringUtils.isEmpty(modelNamePrefix)) { + name = modelNamePrefix + "_" + name; + } + + if (!StringUtils.isEmpty(modelNameSuffix)) { + name = name + "_" + modelNameSuffix; + } + + // camelize the model name + // phone_number => PhoneNumber + name = camelize(sanitizeName(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 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) || "Short".equals(type)) { + if (example == null) { + example = "56"; + } + } else if ("Long".equals(type)) { + if (example == null) { + example = "56"; + } + example = example + "L"; + } else if ("Float".equals(type)) { + if (example == null) { + example = "3.4"; + } + example = example + "F"; + } else if ("Double".equals(type)) { + example = "3.4"; + example = example + "D"; + } else if ("Boolean".equals(type)) { + if (example == null) { + example = "true"; + } + } else if ("File".equals(type)) { + if (example == null) { + example = "/path/to/file"; + } + example = "new File(\"" + escapeText(example) + "\")"; + } else if ("Date".equals(type)) { + example = "new Date()"; + } else if (!languageSpecificPrimitives.contains(type)) { + // type is a model class, e.g. User + example = "new " + type + "()"; + } + + if (example == null) { + example = "null"; + } else if (Boolean.TRUE.equals(p.isListContainer)) { + example = "Arrays.asList(" + example + ")"; + } else if (Boolean.TRUE.equals(p.isMapContainer)) { + example = "new HashMap()"; + } + + p.example = example; + } + + @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"); + } + + 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 void processOpts() { + super.processOpts(); + + if (additionalProperties.containsKey(CodegenConstants.INVOKER_PACKAGE)) { + this.setInvokerPackage((String) additionalProperties.get(CodegenConstants.INVOKER_PACKAGE)); + this.setRequestPackage(invokerPackage + ".request"); + this.setAuthPackage(invokerPackage + ".auth"); + } else { + //not set, use default to be passed to template + additionalProperties.put(CodegenConstants.INVOKER_PACKAGE, invokerPackage); + additionalProperties.put("requestPackage", requestPackage); + additionalProperties.put("authPackage", authPackage); + } + + if (additionalProperties.containsKey(CodegenConstants.GROUP_ID)) { + this.setGroupId((String) additionalProperties.get(CodegenConstants.GROUP_ID)); + } else { + //not set, use to be passed to template + additionalProperties.put(CodegenConstants.GROUP_ID, groupId); + } + + if (additionalProperties.containsKey(CodegenConstants.ARTIFACT_ID)) { + this.setArtifactId((String) additionalProperties.get(CodegenConstants.ARTIFACT_ID)); + } else { + //not set, use to be passed to template + additionalProperties.put(CodegenConstants.ARTIFACT_ID, artifactId); + } + + if (additionalProperties.containsKey(CodegenConstants.ARTIFACT_VERSION)) { + this.setArtifactVersion((String) additionalProperties.get(CodegenConstants.ARTIFACT_VERSION)); + } else { + //not set, use to be passed to template + additionalProperties.put(CodegenConstants.ARTIFACT_VERSION, artifactVersion); + } + + if (additionalProperties.containsKey(CodegenConstants.SOURCE_FOLDER)) { + this.setSourceFolder((String) additionalProperties.get(CodegenConstants.SOURCE_FOLDER)); + } + + if (additionalProperties.containsKey(USE_ANDROID_MAVEN_GRADLE_PLUGIN)) { + this.setUseAndroidMavenGradlePlugin(Boolean.valueOf((String) additionalProperties + .get(USE_ANDROID_MAVEN_GRADLE_PLUGIN))); + } else { + additionalProperties.put(USE_ANDROID_MAVEN_GRADLE_PLUGIN, useAndroidMavenGradlePlugin); + } + + if (additionalProperties.containsKey(ANDROID_GRADLE_VERSION)) { + this.setAndroidGradleVersion((String) additionalProperties.get(ANDROID_GRADLE_VERSION)); + } + + if (additionalProperties.containsKey(ANDROID_SDK_VERSION)) { + this.setAndroidSdkVersion((String) additionalProperties.get(ANDROID_SDK_VERSION)); + } + + if (additionalProperties.containsKey(ANDROID_BUILD_TOOLS_VERSION)) { + this.setAndroidBuildToolsVersion((String) additionalProperties.get(ANDROID_BUILD_TOOLS_VERSION)); + } + + if (additionalProperties.containsKey(CodegenConstants.LIBRARY)) { + this.setLibrary((String) additionalProperties.get(CodegenConstants.LIBRARY)); + } + + if (additionalProperties.containsKey(CodegenConstants.SERIALIZABLE_MODEL)) { + this.setSerializableModel(Boolean.valueOf(additionalProperties.get(CodegenConstants.SERIALIZABLE_MODEL).toString())); + } + + // need to put back serializableModel (boolean) into additionalProperties as value in additionalProperties is string + additionalProperties.put(CodegenConstants.SERIALIZABLE_MODEL, serializableModel); + + //make api and model doc path available in mustache template + additionalProperties.put("apiDocPath", apiDocPath); + additionalProperties.put("modelDocPath", modelDocPath); + + if (StringUtils.isEmpty(getLibrary())) { + setLibrary("volley"); // set volley as the default library + } + + // determine which file (mustache) to add based on library + if ("volley".equals(getLibrary())) { + addSupportingFilesForVolley(); + } else if ("httpclient".equals(getLibrary())) { + addSupportingFilesForHttpClient(); + } 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() { + return useAndroidMavenGradlePlugin; + } + + public String getAndroidGradleVersion() { + return androidGradleVersion; + } + + public String getAndroidSdkVersion() { + return androidSdkVersion; + } + + public String getAndroidBuildToolsVersion() { + return androidBuildToolsVersion; + } + + public void setUseAndroidMavenGradlePlugin(Boolean useAndroidMavenGradlePlugin) { + this.useAndroidMavenGradlePlugin = useAndroidMavenGradlePlugin; + } + + public void setAndroidGradleVersion(String androidGradleVersion) { + this.androidGradleVersion = androidGradleVersion; + } + + public void setAndroidSdkVersion(String androidSdkVersion) { + this.androidSdkVersion = androidSdkVersion; + } + + public void setAndroidBuildToolsVersion(String androidBuildToolsVersion) { + this.androidBuildToolsVersion = androidBuildToolsVersion; + } + + public void setInvokerPackage(String invokerPackage) { + this.invokerPackage = invokerPackage; + } + + public void setRequestPackage(String requestPackage) { + this.requestPackage = requestPackage; + } + + public void setAuthPackage(String authPackage) { + this.authPackage = authPackage; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public void setArtifactId(String artifactId) { + this.artifactId = artifactId; + } + + public void setArtifactVersion(String artifactVersion) { + this.artifactVersion = artifactVersion; + } + + public void setSourceFolder(String sourceFolder) { + this.sourceFolder = sourceFolder; + } + + public void setSerializableModel(Boolean serializableModel) { + this.serializableModel = serializableModel; + } + + @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/DartClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/DartClientCodegen.java new file mode 100644 index 00000000000..a39651244dc --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/DartClientCodegen.java @@ -0,0 +1,475 @@ +package org.openapitools.codegen.languages; + +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 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 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; + +public class DartClientCodegen extends DefaultCodegen implements CodegenConfig { + public static final String BROWSER_CLIENT = "browserClient"; + public static final String PUB_NAME = "pubName"; + public static final String PUB_VERSION = "pubVersion"; + public static final String PUB_DESCRIPTION = "pubDescription"; + public static final String USE_ENUM_EXTENSION = "useEnumExtension"; + protected boolean browserClient = true; + protected String pubName = "swagger"; + protected String pubVersion = "1.0.0"; + protected String pubDescription = "Swagger API client"; + protected boolean useEnumExtension = false; + protected String sourceFolder = ""; + protected String apiDocPath = "docs/"; + protected String modelDocPath = "docs/"; + + public DartClientCodegen() { + super(); + + // clear import mapping (from default generator) as dart does not use it + // at the moment + importMapping.clear(); + + outputFolder = "generated-code/dart"; + modelTemplateFiles.put("model.mustache", ".dart"); + apiTemplateFiles.put("api.mustache", ".dart"); + embeddedTemplateDir = templateDir = "dart"; + apiPackage = "lib.api"; + modelPackage = "lib.model"; + modelDocTemplateFiles.put("object_doc.mustache", ".md"); + apiDocTemplateFiles.put("api_doc.mustache", ".md"); + + setReservedWordsLowerCase( + Arrays.asList( + "abstract", "as", "assert", "async", "async*", "await", + "break", "case", "catch", "class", "const", "continue", + "default", "deferred", "do", "dynamic", "else", "enum", + "export", "external", "extends", "factory", "false", "final", + "finally", "for", "get", "if", "implements", "import", "in", + "is", "library", "new", "null", "operator", "part", "rethrow", + "return", "set", "static", "super", "switch", "sync*", "this", + "throw", "true", "try", "typedef", "var", "void", "while", + "with", "yield", "yield*" ) + ); + + languageSpecificPrimitives = new HashSet( + Arrays.asList( + "String", + "bool", + "int", + "num", + "double") + ); + instantiationTypes.put("array", "List"); + instantiationTypes.put("map", "Map"); + + typeMapping = new HashMap(); + typeMapping.put("Array", "List"); + typeMapping.put("array", "List"); + typeMapping.put("List", "List"); + typeMapping.put("boolean", "bool"); + typeMapping.put("string", "String"); + typeMapping.put("char", "String"); + typeMapping.put("int", "int"); + typeMapping.put("long", "int"); + typeMapping.put("short", "int"); + typeMapping.put("number", "num"); + typeMapping.put("float", "double"); + typeMapping.put("double", "double"); + typeMapping.put("object", "Object"); + typeMapping.put("integer", "int"); + typeMapping.put("Date", "DateTime"); + typeMapping.put("date", "DateTime"); + typeMapping.put("File", "MultipartFile"); + typeMapping.put("UUID", "String"); + //TODO binary should be mapped to byte array + // mapped to String as a workaround + typeMapping.put("binary", "String"); + + cliOptions.add(new CliOption(BROWSER_CLIENT, "Is the client browser based")); + cliOptions.add(new CliOption(PUB_NAME, "Name in generated pubspec")); + cliOptions.add(new CliOption(PUB_VERSION, "Version in generated pubspec")); + cliOptions.add(new CliOption(PUB_DESCRIPTION, "Description in generated pubspec")); + cliOptions.add(new CliOption(USE_ENUM_EXTENSION, "Allow the 'x-enum-values' extension for enums")); + cliOptions.add(new CliOption(CodegenConstants.SOURCE_FOLDER, "source folder for generated code")); + } + + @Override + public CodegenType getTag() { + return CodegenType.CLIENT; + } + + @Override + public String getName() { + return "dart"; + } + + @Override + public String getHelp() { + return "Generates a Dart client library."; + } + + @Override + public void processOpts() { + super.processOpts(); + + if (additionalProperties.containsKey(BROWSER_CLIENT)) { + this.setBrowserClient(convertPropertyToBooleanAndWriteBack(BROWSER_CLIENT)); + } else { + //not set, use to be passed to template + additionalProperties.put(BROWSER_CLIENT, browserClient); + } + + if (additionalProperties.containsKey(PUB_NAME)) { + this.setPubName((String) additionalProperties.get(PUB_NAME)); + } else { + //not set, use to be passed to template + additionalProperties.put(PUB_NAME, pubName); + } + + if (additionalProperties.containsKey(PUB_VERSION)) { + this.setPubVersion((String) additionalProperties.get(PUB_VERSION)); + } else { + //not set, use to be passed to template + additionalProperties.put(PUB_VERSION, pubVersion); + } + + if (additionalProperties.containsKey(PUB_DESCRIPTION)) { + this.setPubDescription((String) additionalProperties.get(PUB_DESCRIPTION)); + } else { + //not set, use to be passed to template + additionalProperties.put(PUB_DESCRIPTION, pubDescription); + } + + if (additionalProperties.containsKey(USE_ENUM_EXTENSION)) { + this.setUseEnumExtension(convertPropertyToBooleanAndWriteBack(USE_ENUM_EXTENSION)); + } else { + // Not set, use to be passed to template. + additionalProperties.put(USE_ENUM_EXTENSION, useEnumExtension); + } + + if (additionalProperties.containsKey(CodegenConstants.SOURCE_FOLDER)) { + this.setSourceFolder((String) additionalProperties.get(CodegenConstants.SOURCE_FOLDER)); + } + + // 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())); + } + + // make api and model doc path available in mustache template + additionalProperties.put("apiDocPath", apiDocPath); + additionalProperties.put("modelDocPath", modelDocPath); + + final String libFolder = sourceFolder + File.separator + "lib"; + supportingFiles.add(new SupportingFile("pubspec.mustache", "", "pubspec.yaml")); + supportingFiles.add(new SupportingFile("analysis_options.mustache", "", ".analysis_options")); + supportingFiles.add(new SupportingFile("api_client.mustache", libFolder, "api_client.dart")); + supportingFiles.add(new SupportingFile("api_exception.mustache", libFolder, "api_exception.dart")); + supportingFiles.add(new SupportingFile("api_helper.mustache", libFolder, "api_helper.dart")); + supportingFiles.add(new SupportingFile("apilib.mustache", libFolder, "api.dart")); + + final String authFolder = sourceFolder + File.separator + "lib" + File.separator + "auth"; + supportingFiles.add(new SupportingFile("auth/authentication.mustache", authFolder, "authentication.dart")); + supportingFiles.add(new SupportingFile("auth/http_basic_auth.mustache", authFolder, "http_basic_auth.dart")); + supportingFiles.add(new SupportingFile("auth/api_key_auth.mustache", authFolder, "api_key_auth.dart")); + supportingFiles.add(new SupportingFile("auth/oauth.mustache", authFolder, "oauth.dart")); + supportingFiles.add(new SupportingFile("git_push.sh.mustache", "", "git_push.sh")); + supportingFiles.add(new SupportingFile("gitignore.mustache", "", ".gitignore")); + supportingFiles.add(new SupportingFile("README.mustache", "", "README.md")); + } + + @Override + public String escapeReservedWord(String name) { + return name + "_"; + } + + @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 apiDocFileFolder() { + return (outputFolder + "/" + apiDocPath).replace('/', File.separatorChar); + } + + @Override + public String modelDocFileFolder() { + return (outputFolder + "/" + modelDocPath).replace('/', File.separatorChar); + } + + @Override + public String toVarName(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'. + + // 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); + + if (name.matches("^\\d.*")) { + name = "n" + name; + } + + if (isReservedWord(name)) { + 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) { + // model name cannot use reserved keyword, e.g. return + if (isReservedWord(name)) { + LOGGER.warn(name + " (reserved word) cannot be used as model filename. Renamed to " + camelize("model_" + name)); + name = "model_" + name; // e.g. return => ModelReturn (after camelize) + } + + // camelize the model name + // phone_number => PhoneNumber + return camelize(name); + } + + @Override + public String toModelFilename(String name) { + return underscore(toModelName(name)); + } + + @Override + public String toApiFilename(String name) { + return underscore(toApiName(name)); + } + + @Override + public String toDefaultValue(Schema p) { + if (p instanceof MapSchema) { + return "{}"; + } else if (p instanceof ArraySchema) { + return "[]"; + } + return super.toDefaultValue(p); + } + + @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) + ""; + } + return super.getTypeDeclaration(p); + } + + @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 Map postProcessModels(Map objs) { + return postProcessModelsEnum(objs); + } + + @Override + public Map postProcessModelsEnum(Map objs) { + List models = (List) objs.get("models"); + for (Object _mo : models) { + Map mo = (Map) _mo; + CodegenModel cm = (CodegenModel) mo.get("model"); + boolean succes = buildEnumFromVendorExtension(cm) || + buildEnumFromValues(cm); + for (CodegenProperty var : cm.vars) { + updateCodegenPropertyEnum(var); + } + } + return objs; + } + + /** + * Builds the set of enum members from their declared value. + * + * @return {@code true} if the enum was built + */ + private boolean buildEnumFromValues(CodegenModel cm) { + if (!cm.isEnum || cm.allowableValues == null) { + return false; + } + Map allowableValues = cm.allowableValues; + List values = (List) allowableValues.get("values"); + List> enumVars = + new ArrayList>(); + String commonPrefix = findCommonPrefixOfVars(values); + int truncateIdx = commonPrefix.length(); + for (Object value : values) { + Map enumVar = new HashMap(); + String enumName; + if (truncateIdx == 0) { + enumName = value.toString(); + } else { + enumName = value.toString().substring(truncateIdx); + if ("".equals(enumName)) { + enumName = value.toString(); + } + } + enumVar.put("name", toEnumVarName(enumName, cm.dataType)); + enumVar.put("value", toEnumValue(value.toString(), cm.dataType)); + enumVars.add(enumVar); + } + cm.allowableValues.put("enumVars", enumVars); + return true; + } + + /** + * Builds the set of enum members from a vendor extension. + * + * @return {@code true} if the enum was built + */ + private boolean buildEnumFromVendorExtension(CodegenModel cm) { + if (!cm.isEnum || cm.allowableValues == null || + !useEnumExtension || + !cm.vendorExtensions.containsKey("x-enum-values")) { + return false; + } + Object extension = cm.vendorExtensions.get("x-enum-values"); + List> values = + (List>) extension; + List> enumVars = + new ArrayList>(); + for (Map value : values) { + Map enumVar = new HashMap(); + String name = camelize((String) value.get("identifier"), true); + if (isReservedWord(name)) { + name = escapeReservedWord(name); + } + enumVar.put("name", name); + enumVar.put("value", toEnumValue( + value.get("numericValue").toString(), cm.dataType)); + if (value.containsKey("description")) { + enumVar.put("description", value.get("description").toString()); + } + enumVars.add(enumVar); + } + cm.allowableValues.put("enumVars", enumVars); + return true; + } + + @Override + public String toEnumVarName(String value, String datatype) { + if (value.length() == 0) { + return "empty"; + } + String var = value.replaceAll("\\W+", "_"); + if ("number".equalsIgnoreCase(datatype) || + "int".equalsIgnoreCase(datatype)) { + var = "Number" + var; + } + return escapeReservedWord(camelize(var, true)); + } + + @Override + public String toEnumValue(String value, String datatype) { + if ("number".equalsIgnoreCase(datatype) || + "int".equalsIgnoreCase(datatype)) { + return value; + } else { + return "\"" + escapeText(value) + "\""; + } + } + + @Override + public String toOperationId(String operationId) { + // 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 camelize(operationId, true); + } + + public void setBrowserClient(boolean browserClient) { + this.browserClient = browserClient; + } + + public void setPubName(String pubName) { + this.pubName = pubName; + } + + public void setPubVersion(String pubVersion) { + this.pubVersion = pubVersion; + } + + public void setPubDescription(String pubDescription) { + this.pubDescription = pubDescription; + } + + public void setUseEnumExtension(boolean useEnumExtension) { + this.useEnumExtension = useEnumExtension; + } + + public void setSourceFolder(String sourceFolder) { + this.sourceFolder = sourceFolder; + } + + @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/ElixirClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ElixirClientCodegen.java new file mode 100644 index 00000000000..164074429ea --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ElixirClientCodegen.java @@ -0,0 +1,759 @@ +package org.openapitools.codegen.languages; + +import com.samskivert.mustache.Mustache; +import com.samskivert.mustache.Template; +import org.openapitools.codegen.*; +import io.swagger.v3.oas.models.*; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.media.*; + +import org.apache.commons.lang3.StringUtils; + +import java.io.IOException; +import java.io.Writer; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class ElixirClientCodegen extends DefaultCodegen implements CodegenConfig { + protected String apiVersion = "1.0.0"; + protected String moduleName; + protected static final String defaultModuleName = "Swagger.Client"; + + // This is the name of elixir project name; + protected static final String defaultPackageName = "swagger_client"; + + String supportedElixirVersion = "1.4"; + List extraApplications = Arrays.asList(":logger"); + List deps = Arrays.asList( + "{:tesla, \"~> 0.8\"}", + "{:poison, \">= 1.0.0\"}" + ); + + public ElixirClientCodegen() { + super(); + + // set the output folder here + outputFolder = "generated-code/elixir"; + + /* + * 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", // the template to use + ".ex"); // the extension for each file to write + + /** + * 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.mustache", // the template to use + ".ex"); // 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. + */ + templateDir = "elixir"; + + /** + * Reserved words. Override this with reserved words specific to your language + * Ref: https://github.com/itsgreggreg/elixir_quick_reference#reserved-words + */ + reservedWords = new HashSet( + Arrays.asList( + "nil", + "true", + "false", + "__MODULE__", + "__FILE__", + "__DIR__", + "__ENV__", + "__CALLER__") + ); + + /** + * Additional Properties. These values can be passed to the templates and + * are available in models, apis, and supporting files + */ + additionalProperties.put("apiVersion", apiVersion); + + /** + * 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.md.mustache", // the input template or file + "", // the destination folder, relative `outputFolder` + "README.md") // the output file + ); + supportingFiles.add(new SupportingFile("config.exs.mustache", + "config", + "config.exs") + ); + supportingFiles.add(new SupportingFile("mix.exs.mustache", + "", + "mix.exs") + ); + supportingFiles.add(new SupportingFile("test_helper.exs.mustache", + "test", + "test_helper.exs") + ); + supportingFiles.add(new SupportingFile("gitignore.mustache", + "", + ".gitignore") + ); + + /** + * Language Specific Primitives. These types will not trigger imports by + * the client generator + */ + languageSpecificPrimitives = new HashSet( + Arrays.asList( + "Integer", + "Float", + "Boolean", + "String", + "List", + "Atom", + "Map", + "Tuple", + "PID", + "DateTime" + ) + ); + + // ref: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types + typeMapping = new HashMap(); + typeMapping.put("integer", "Integer"); + typeMapping.put("long", "Integer"); + typeMapping.put("number", "Float"); + typeMapping.put("float", "Float"); + typeMapping.put("double", "Float"); + typeMapping.put("string", "String"); + typeMapping.put("byte", "Integer"); + typeMapping.put("boolean", "Boolean"); + typeMapping.put("Date", "DateTime"); + typeMapping.put("DateTime", "DateTime"); + typeMapping.put("file", "String"); + typeMapping.put("map", "Map"); + typeMapping.put("array", "List"); + typeMapping.put("list", "List"); + // typeMapping.put("object", "Map"); + typeMapping.put("binary", "String"); + typeMapping.put("ByteArray", "String"); + typeMapping.put("UUID", "String"); + + cliOptions.add(new CliOption(CodegenConstants.INVOKER_PACKAGE, "The main namespace to use for all classes. e.g. Yay.Pets")); + cliOptions.add(new CliOption("licenseHeader", "The license header to prepend to the top of all source files.")); + cliOptions.add(new CliOption(CodegenConstants.PACKAGE_NAME, "Elixir package name (convention: lowercase).")); + } + + /** + * 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 "elixir"; + } + + /** + * 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 an elixir client library (alpha)."; + } + + @Override + public void processOpts() { + super.processOpts(); + additionalProperties.put("supportedElixirVersion", supportedElixirVersion); + additionalProperties.put("extraApplications", join(",", extraApplications)); + additionalProperties.put("deps", deps); + additionalProperties.put("underscored", new Mustache.Lambda() { + @Override + public void execute(Template.Fragment fragment, Writer writer) throws IOException { + writer.write(underscored(fragment.execute())); + } + }); + additionalProperties.put("modulized", new Mustache.Lambda() { + @Override + public void execute(Template.Fragment fragment, Writer writer) throws IOException { + writer.write(modulized(fragment.execute())); + } + }); + + if (additionalProperties.containsKey(CodegenConstants.INVOKER_PACKAGE)) { + setModuleName((String) additionalProperties.get(CodegenConstants.INVOKER_PACKAGE)); + } + } + + @Override + public void preprocessOpenAPI(OpenAPI openAPI) { + Info info = openAPI.getInfo(); + if (moduleName == null) { + if (info.getTitle() != null) { + // default to the appName (from title field) + setModuleName(modulized(escapeText(info.getTitle()))); + } else { + setModuleName(defaultModuleName); + } + } + additionalProperties.put("moduleName", moduleName); + + if (!additionalProperties.containsKey(CodegenConstants.PACKAGE_NAME)) { + additionalProperties.put(CodegenConstants.PACKAGE_NAME, underscored(moduleName)); + } + + supportingFiles.add(new SupportingFile("connection.ex.mustache", + sourceFolder(), + "connection.ex")); + + supportingFiles.add(new SupportingFile("request_builder.ex.mustache", + sourceFolder(), + "request_builder.ex")); + + + supportingFiles.add(new SupportingFile("deserializer.ex.mustache", + sourceFolder(), + "deserializer.ex")); + } + + @Override + public Map postProcessOperations(Map objs) { + Map operations = (Map) super.postProcessOperations(objs).get("operations"); + List os = (List) operations.get("operation"); + List newOs = new ArrayList(); + Pattern pattern = Pattern.compile("\\{([^\\}]+)\\}([^\\{]*)"); + for (CodegenOperation o : os) { + ArrayList pathTemplateNames = new ArrayList(); + Matcher matcher = pattern.matcher(o.path); + StringBuffer buffer = new StringBuffer(); + while (matcher.find()) { + String pathTemplateName = matcher.group(1); + matcher.appendReplacement(buffer, "#{" + underscore(pathTemplateName) + "}" + "$2"); + pathTemplateNames.add(pathTemplateName); + } + ExtendedCodegenOperation eco = new ExtendedCodegenOperation(o); + if (buffer.toString().isEmpty()) { + eco.setReplacedPathName(o.path); + } else { + eco.setReplacedPathName(buffer.toString()); + } + eco.setPathTemplateNames(pathTemplateNames); + + // detect multipart form types + if (eco.hasConsumes == Boolean.TRUE) { + Map firstType = eco.consumes.get(0); + if (firstType != null) { + if ("multipart/form-data".equals(firstType.get("mediaType"))) { + eco.isMultipart = Boolean.TRUE; + } + } + } + + newOs.add(eco); + } + operations.put("operation", newOs); + return objs; + } + + @Override + public CodegenModel fromModel(String name, Schema model, Map allDefinitions) { + CodegenModel cm = super.fromModel(name, model, allDefinitions); + return new ExtendedCodegenModel(cm); + } + + // We should use String.join if we can use Java8 + String join(CharSequence charSequence, Iterable iterable) { + StringBuilder buf = new StringBuilder(); + for (String str : iterable) { + if (0 < buf.length()) { + buf.append((charSequence)); + } + buf.append(str); + } + return buf.toString(); + } + + String underscored(String words) { + ArrayList underscoredWords = new ArrayList(); + for (String word : words.split(" ")) { + underscoredWords.add(underscore(word)); + } + return join("_", underscoredWords); + } + + String modulized(String words) { + ArrayList modulizedWords = new ArrayList(); + for (String word : words.split(" ")) { + modulizedWords.add(camelize(word)); + } + return join("", modulizedWords); + } + + /** + * 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 + } + + private String sourceFolder() { + ArrayList underscoredWords = new ArrayList(); + for (String word : moduleName.split("\\.")) { + underscoredWords.add(underscore(word)); + } + return "lib/" + join("/", underscoredWords); + } + + /** + * Location to write model files. You can use the modelPackage() as defined when the class is + * instantiated + */ + public String modelFileFolder() { + return outputFolder + "/" + sourceFolder() + "/" + "model"; + } + + /** + * Location to write api files. You can use the apiPackage() as defined when the class is + * instantiated + */ + @Override + public String apiFileFolder() { + return outputFolder + "/" + sourceFolder() + "/" + "api"; + } + + @Override + public String toApiName(String name) { + if (name.length() == 0) { + return "Default"; + } + return camelize(name); + } + + @Override + public String toApiFilename(String name) { + // replace - with _ e.g. created-at => created_at + name = name.replaceAll("-", "_"); + + // e.g. PetApi.go => pet_api.go + return underscore(name); + } + + @Override + public String toModelName(String name) { + // camelize the model name + // phone_number => PhoneNumber + return camelize(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 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"); + } + + return camelize(sanitizeName(operationId)); + } + + /** + * 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) { + 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 "%{optional(String.t) => " + getTypeDeclaration(inner) + "}"; + } else if (p instanceof PasswordSchema) { + return "String.t"; + } else if (p instanceof EmailSchema) { + return "String.t"; + } else if (p instanceof ByteArraySchema) { + return "binary()"; + } else if (p instanceof StringSchema) { + return "String.t"; + } else if (p instanceof DateSchema) { + return "Date.t"; + } else if (p instanceof UUIDSchema) { + return "String.t"; + } else if (p instanceof DateTimeSchema) { + return "DateTime.t"; + } else if (p instanceof ObjectSchema) { + // How to map it? + return super.getTypeDeclaration(p); + } else if (p instanceof IntegerSchema) { + return "integer()"; + } else if (p instanceof NumberSchema) { + return "float()"; + } else if (p instanceof BinarySchema) { + return "binary()"; + } else if (p instanceof BooleanSchema) { + return "boolean()"; + } else if (!StringUtils.isEmpty(p.get$ref())) { // model + // How to map it? + return super.getTypeDeclaration(p); + } else if (p instanceof FileSchema) { + return "String.t"; + } + return super.getTypeDeclaration(p); + } + + /** + * 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); + } else + type = openAPIType; + return toModelName(type); + } + + class ExtendedCodegenOperation extends CodegenOperation { + private List pathTemplateNames = new ArrayList(); + private String replacedPathName; + + public ExtendedCodegenOperation(CodegenOperation o) { + super(); + + // Copy all fields of CodegenOperation + this.responseHeaders.addAll(o.responseHeaders); + this.hasAuthMethods = o.hasAuthMethods; + this.hasConsumes = o.hasConsumes; + this.hasProduces = o.hasProduces; + this.hasParams = o.hasParams; + this.hasOptionalParams = o.hasOptionalParams; + this.returnTypeIsPrimitive = o.returnTypeIsPrimitive; + this.returnSimpleType = o.returnSimpleType; + this.subresourceOperation = o.subresourceOperation; + this.isMapContainer = o.isMapContainer; + this.isListContainer = o.isListContainer; + this.isMultipart = o.isMultipart; + this.hasMore = o.hasMore; + this.isResponseBinary = o.isResponseBinary; + this.hasReference = o.hasReference; + this.isRestfulIndex = o.isRestfulIndex; + this.isRestfulShow = o.isRestfulShow; + this.isRestfulCreate = o.isRestfulCreate; + this.isRestfulUpdate = o.isRestfulUpdate; + this.isRestfulDestroy = o.isRestfulDestroy; + this.isRestful = o.isRestful; + this.path = o.path; + this.operationId = o.operationId; + this.returnType = o.returnType; + this.httpMethod = o.httpMethod; + this.returnBaseType = o.returnBaseType; + this.returnContainer = o.returnContainer; + this.summary = o.summary; + this.unescapedNotes = o.unescapedNotes; + this.notes = o.notes; + this.baseName = o.baseName; + this.defaultResponse = o.defaultResponse; + this.discriminator = o.discriminator; + this.consumes = o.consumes; + this.produces = o.produces; + this.bodyParam = o.bodyParam; + this.allParams = o.allParams; + this.bodyParams = o.bodyParams; + this.pathParams = o.pathParams; + this.queryParams = o.queryParams; + this.headerParams = o.headerParams; + this.formParams = o.formParams; + this.authMethods = o.authMethods; + this.tags = o.tags; + this.responses = o.responses; + this.imports = o.imports; + this.examples = o.examples; + this.externalDocs = o.externalDocs; + this.vendorExtensions = o.vendorExtensions; + this.nickname = o.nickname; + this.operationIdLowerCase = o.operationIdLowerCase; + this.operationIdCamelCase = o.operationIdCamelCase; + } + + public List getPathTemplateNames() { + return pathTemplateNames; + } + + public void setPathTemplateNames(List pathTemplateNames) { + this.pathTemplateNames = pathTemplateNames; + } + + public String getReplacedPathName() { + return replacedPathName; + } + + public void setReplacedPathName(String replacedPathName) { + this.replacedPathName = replacedPathName; + } + + public String typespec() { + StringBuilder sb = new StringBuilder("@spec "); + sb.append(underscore(operationId)); + sb.append("(Tesla.Env.client, "); + + for (CodegenParameter param : allParams) { + if (param.required) { + buildTypespec(param, sb); + sb.append(", "); + } + } + + sb.append("keyword()) :: {:ok, "); + if (returnBaseType == null) { + sb.append("nil"); + } else if (returnSimpleType) { + if (!returnTypeIsPrimitive) { + sb.append(moduleName); + sb.append(".Model."); + } + sb.append(returnBaseType); + sb.append(".t"); + } else if (returnContainer == null) { + sb.append(returnBaseType); + sb.append(".t"); + } else { + if (returnContainer.equals("array")) { + sb.append("list("); + if (!returnTypeIsPrimitive) { + sb.append(moduleName); + sb.append(".Model."); + } + sb.append(returnBaseType); + sb.append(".t)"); + } else if (returnContainer.equals("map")) { + sb.append("map()"); + } + } + sb.append("} | {:error, Tesla.Env.t}"); + return sb.toString(); + } + + private void buildTypespec(CodegenParameter param, StringBuilder sb) { + if (param.dataType == null) { + sb.append("nil"); + } else if (param.isListContainer) { + // list() + sb.append("list("); + if (param.isBodyParam) { + buildTypespec(param.items.items, sb); + } else { + buildTypespec(param.items, sb); + } + sb.append(")"); + } else if (param.isMapContainer) { + // %{optional(String.t) => } + sb.append("%{optional(String.t) => "); + buildTypespec(param.items, sb); + sb.append("}"); + } else if (param.isPrimitiveType) { + // like `integer()`, `String.t` + sb.append(param.dataType); + } else if (param.isFile) { + sb.append("String.t"); + } else { + // .Model..t + sb.append(moduleName); + sb.append(".Model."); + sb.append(param.dataType); + sb.append(".t"); + } + } + + private void buildTypespec(CodegenProperty property, StringBuilder sb) { + if (property == null) { + LOGGER.warn("CodegenProperty cannot be null"); + } else if (property.isListContainer) { + sb.append("list("); + buildTypespec(property.items, sb); + sb.append(")"); + } else if (property.isMapContainer) { + sb.append("%{optional(String.t) => "); + buildTypespec(property.items, sb); + sb.append("}"); + } else if (property.isPrimitiveType) { + sb.append(property.baseType); + sb.append(".t"); + } else { + sb.append(moduleName); + sb.append(".Model."); + sb.append(property.baseType); + sb.append(".t"); + } + } + + public String decodedStruct() { + // Let Poison decode the entire response into a generic blob + if (isMapContainer) { + return ""; + } + // Primitive return type, don't even try to decode + if (returnBaseType == null || (returnSimpleType && returnTypeIsPrimitive)) { + return "false"; + } + StringBuilder sb = new StringBuilder(); + if (isListContainer) { + sb.append("["); + } + sb.append("%"); + sb.append(moduleName); + sb.append(".Model."); + sb.append(returnBaseType); + sb.append("{}"); + if (isListContainer) { + sb.append("]"); + } + return sb.toString(); + } + } + + class ExtendedCodegenModel extends CodegenModel { + public boolean hasImports; + + public ExtendedCodegenModel(CodegenModel cm) { + super(); + + // Copy all fields of CodegenModel + this.parent = cm.parent; + this.parentSchema = cm.parentSchema; + this.parentModel = cm.parentModel; + this.interfaceModels = cm.interfaceModels; + this.children = cm.children; + this.name = cm.name; + this.classname = cm.classname; + this.title = cm.title; + this.description = cm.description; + this.classVarName = cm.classVarName; + this.modelJson = cm.modelJson; + this.dataType = cm.dataType; + this.xmlPrefix = cm.xmlPrefix; + this.xmlNamespace = cm.xmlNamespace; + this.xmlName = cm.xmlName; + this.classFilename = cm.classFilename; + this.unescapedDescription = cm.unescapedDescription; + this.discriminator = cm.discriminator; + this.defaultValue = cm.defaultValue; + this.arrayModelType = cm.arrayModelType; + this.isAlias = cm.isAlias; + this.vars = cm.vars; + this.requiredVars = cm.requiredVars; + this.optionalVars = cm.optionalVars; + this.readOnlyVars = cm.readOnlyVars; + this.readWriteVars = cm.readWriteVars; + this.allVars = cm.allVars; + this.parentVars = cm.parentVars; + this.allowableValues = cm.allowableValues; + this.mandatory = cm.mandatory; + this.allMandatory = cm.allMandatory; + this.imports = cm.imports; + this.hasVars = cm.hasVars; + this.emptyVars = cm.emptyVars; + this.hasMoreModels = cm.hasMoreModels; + this.hasEnums = cm.hasEnums; + this.isEnum = cm.isEnum; + this.hasRequired = cm.hasRequired; + this.hasOptional = cm.hasOptional; + this.isArrayModel = cm.isArrayModel; + this.hasChildren = cm.hasChildren; + this.hasOnlyReadOnly = cm.hasOnlyReadOnly; + this.externalDocumentation = cm.externalDocumentation; + this.vendorExtensions = cm.vendorExtensions; + this.additionalPropertiesType = cm.additionalPropertiesType; + + this.hasImports = !this.imports.isEmpty(); + } + + public boolean hasComplexVars() { + for (CodegenProperty p : vars) { + if (!p.isPrimitiveType) { + return true; + } + } + return false; + } + } + + @Override + public String escapeQuotationMark(String input) { + return input.replace("\"", ""); + } + + @Override + public String escapeUnsafeCharacters(String input) { + // no need to escape as Elixir does not support multi-line comments + return input; + } + + public void setModuleName(String moduleName) { + this.moduleName = moduleName; + } +} 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/LumenServerCodegen.java new file mode 100644 index 00000000000..78513fd0bca --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/LumenServerCodegen.java @@ -0,0 +1,128 @@ +package org.openapitools.codegen.languages; + +import org.openapitools.codegen.*; +import io.swagger.models.properties.*; + +import java.util.*; +import java.io.File; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.TreeMap; + +public class LumenServerCodegen 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 + */ + 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 "php-lumen"; + } + + /** + * 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() { + super(); + + embeddedTemplateDir = templateDir = "lumen"; + + /* + * packPath + */ + invokerPackage = "lumen"; + packagePath = ""; + + /* + * Api Package. Optional, if needed, this can be used in templates + */ + apiPackage = "app.Http.Controllers"; + + /* + * Model Package. Optional, if needed, this can be used in templates + */ + modelPackage = "models"; + + // template files want to be ignored + modelTemplateFiles.clear(); + apiTestTemplateFiles.clear(); + apiDocTemplateFiles.clear(); + modelDocTemplateFiles.clear(); + + /* + * Additional Properties. These values can be passed to the templates and + * are available in models, apis, and supporting files + */ + additionalProperties.put("apiVersion", apiVersion); + + /* + * 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("composer.mustache", packagePath + File.separator + srcBasePath, "composer.json")); + supportingFiles.add(new SupportingFile("readme.md", packagePath + File.separator + srcBasePath, "readme.md")); + 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")); + + } + + // override with any special post-processing + @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) { + op.httpMethod = op.httpMethod.toLowerCase(); + // check to see if the path contains ".", which is not supported by Lumen + // ref: https://github.com/swagger-api/swagger-codegen/issues/6897 + if (op.path != null && op.path.contains(".")) { + 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() { + @Override + public int compare(CodegenOperation lhs, CodegenOperation rhs) { + return lhs.path.compareTo(rhs.path); + } + }); + + return objs; + } +} diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PowerShellClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PowerShellClientCodegen.java new file mode 100644 index 00000000000..1a9c4af880f --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PowerShellClientCodegen.java @@ -0,0 +1,407 @@ +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 org.apache.commons.lang3.StringUtils; +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.List; +import java.util.Map; + +import static java.util.UUID.randomUUID; + +public class PowerShellClientCodegen extends DefaultCodegen implements CodegenConfig { + static Logger LOGGER = LoggerFactory.getLogger(PowerShellClientCodegen.class); + + private String packageGuid = "{" + randomUUID().toString().toUpperCase() + "}"; + + protected String sourceFolder = "src"; + protected String packageName = "IO.Swagger"; + protected String csharpClientPath = "$ScriptDir\\csharp\\SwaggerClient"; + protected String apiDocPath = "docs/"; + protected String modelDocPath = "docs/"; + + /** + * Constructs an instance of `PowerShellClientCodegen`. + */ + public PowerShellClientCodegen() { + super(); + + outputFolder = "generated-code" + File.separator + "powershell"; + modelTemplateFiles.put("model.mustache", ".ps1"); + apiTemplateFiles.put("api.mustache", ".ps1"); + modelTestTemplateFiles.put("model_test.mustache", ".ps1"); + apiTestTemplateFiles.put("api_test.mustache", ".ps1"); + modelDocTemplateFiles.clear(); + apiDocTemplateFiles.clear(); + embeddedTemplateDir = templateDir = "powershell"; + apiPackage = packageName + File.separator + "API"; + modelPackage = packageName + File.separator + "Model"; + + // https://blogs.msdn.microsoft.com/powershell/2010/01/07/how-objects-are-sent-to-and-from-remote-sessions/ + languageSpecificPrimitives = new HashSet(Arrays.asList( + "Byte", + "SByte", + "Byte[]", + "Int16", + "Int32", + "Int64", + "UInt16", + "UInt32", + "UInt64", + "Decimal", + "Single", + "Double", + "TimeSpan", + "System.DateTime", + "ProgressRecord", + "Char", + "String", + "XmlDocument", + "SecureString", + "Boolean", + "Guid", + "Uri", + "Version" + )); + + // https://richardspowershellblog.wordpress.com/2009/05/02/powershell-reserved-words/ + reservedWords = new HashSet(Arrays.asList( + "Begin", + "Break", + "Catch", + "Continue", + "Data", + "Do", + "Dynamicparam", + "Else", + "Elseif", + "End", + "Exit", + "Filter", + "Finally", + "For", + "Foreach", + "From", + "Function", + "If", + "In", + "Param", + "Process", + "Return", + "Switch", + "Throw", + "Trap", + "Try", + "Until", + "While", + "Local", + "Private", + "Where" + )); + + + defaultIncludes = new HashSet(Arrays.asList( + "Byte", + "SByte", + "Byte[]", + "Int16", + "Int32", + "Int64", + "UInt16", + "UInt32", + "UInt64", + "Decimal", + "Single", + "Double", + "TimeSpan", + "System.DateTime", + "ProgressRecord", + "Char", + "String", + "XmlDocument", + "SecureString", + "Boolean", + "Guid", + "Uri", + "Version" + )); + + typeMapping = new HashMap(); + typeMapping.put("string", "String"); + typeMapping.put("boolean", "Boolean"); + typeMapping.put("integer", "Int32"); + typeMapping.put("float", "Double"); + typeMapping.put("long", "Int64"); + typeMapping.put("double", "Double"); + typeMapping.put("number", "Decimal"); + typeMapping.put("date-time", "System.DateTime"); + typeMapping.put("date", "System.DateTime"); + typeMapping.put("file", "String"); + typeMapping.put("object", "String"); + typeMapping.put("binary", "String"); + typeMapping.put("Date", "System.DateTime"); + typeMapping.put("DateTime", "System.DateTime"); + + cliOptions.clear(); + cliOptions.add(new CliOption(CodegenConstants.PACKAGE_NAME, "Client package name (e.g. io.swagger.client).").defaultValue(this.packageName)); + cliOptions.add(new CliOption(CodegenConstants.OPTIONAL_PROJECT_GUID, "GUID for PowerShell module (e.g. a27b908d-2a20-467f-bc32-af6f3a654ac5). A random GUID will be generated by default.")); + cliOptions.add(new CliOption("csharpClientPath", "Path to the C# API client generated by Swagger Codegen, e.g. $ScriptDir\\..\\csharp\\SwaggerClient where $ScriptDir is the current directory. NOTE: you will need to generate the C# API client separately.").defaultValue(this.csharpClientPath)); + + } + + public CodegenType getTag() { + return CodegenType.CLIENT; + } + + public String getName() { + return "powershell"; + } + + public String getHelp() { + return "Generates a PowerShell API client (beta). (The dependency C# API client needs to be generated separately."; + } + + public void setPackageName(String packageName) { + this.packageName = packageName; + } + + public void setCsharpClientPath(String csharpClientPath) { + this.csharpClientPath = csharpClientPath; + } + + public void setSourceFolder(String sourceFolder) { + this.sourceFolder = sourceFolder; + } + + public void setPackageGuid(String packageGuid) { + this.packageGuid = packageGuid; + } + + @Override + public void processOpts() { + super.processOpts(); + + if (additionalProperties.containsKey(CodegenConstants.OPTIONAL_PROJECT_GUID)) { + setPackageGuid((String) additionalProperties.get(CodegenConstants.OPTIONAL_PROJECT_GUID)); + } + additionalProperties.put("packageGuid", packageGuid); + + if (additionalProperties.containsKey(CodegenConstants.PACKAGE_NAME)) { + this.setPackageName((String) additionalProperties.get(CodegenConstants.PACKAGE_NAME)); + } else { + additionalProperties.put(CodegenConstants.PACKAGE_NAME, packageName); + } + + if (additionalProperties.containsKey("csharpClientPath")) { + this.setCsharpClientPath((String) additionalProperties.get("csharpClientPath")); + } else { + additionalProperties.put("csharpClientPath", csharpClientPath); + } + + if (additionalProperties.containsKey(CodegenConstants.MODEL_PACKAGE)) { + LOGGER.warn(CodegenConstants.MODEL_PACKAGE + " with " + this.getName() + " generator is ignored. Setting this value independently of " + CodegenConstants.PACKAGE_NAME + " is not currently supported."); + } + + if (additionalProperties.containsKey(CodegenConstants.API_PACKAGE)) { + LOGGER.warn(CodegenConstants.API_PACKAGE + " with " + this.getName() + " generator is ignored. Setting this value independently of " + CodegenConstants.PACKAGE_NAME + " is not currently supported."); + } + + additionalProperties.put(CodegenConstants.API_PACKAGE, apiPackage()); + additionalProperties.put(CodegenConstants.MODEL_PACKAGE, modelPackage()); + + additionalProperties.put("apiDocPath", apiDocPath); + additionalProperties.put("modelDocPath", modelDocPath); + + supportingFiles.add(new SupportingFile("README.mustache", "", "README.md")); + supportingFiles.add(new SupportingFile("Build.ps1.mustache", "", "Build.ps1")); + + final String infrastructureFolder = (sourceFolder + File.separator + packageName + File.separator); + + supportingFiles.add(new SupportingFile("IO.Swagger.psm1.mustache", infrastructureFolder, packageName + ".psm1")); + + // private + supportingFiles.add(new SupportingFile("Get-CommonParameters.ps1", infrastructureFolder + File.separator + "Private" + File.separator, "Get-CommonParameters.ps1")); + supportingFiles.add(new SupportingFile("Out-DebugParameter.ps1", infrastructureFolder + File.separator + "Private" + File.separator, "Out-DebugParameter.ps1")); + + // en-US + supportingFiles.add(new SupportingFile("about_IO.Swagger.help.txt.mustache", infrastructureFolder + File.separator + "en-US" + File.separator + "about_" + packageName + ".help.txt")); + + } + + @Override + public String escapeUnsafeCharacters(String input) { + return input.replace("#>", "#_>").replace("<#", "<_#"); + } + + @Override + public String escapeQuotationMark(String input) { + // remove " to avoid code injection + return input.replace("\"", ""); + } + + @Override + public String apiTestFileFolder() { + return (outputFolder + "/test").replace('/', File.separatorChar); + } + + @Override + public String apiDocFileFolder() { + return (outputFolder + "/" + apiDocPath).replace('/', File.separatorChar); + } + + @Override + public String apiFileFolder() { + return outputFolder + File.separator + sourceFolder + File.separator + apiPackage(); + } + + @Override + public String modelTestFileFolder() { + return (outputFolder + "/test").replace('/', File.separatorChar); + } + + @Override + public String modelDocFileFolder() { + return (outputFolder + "/" + modelDocPath).replace('/', File.separatorChar); + } + + + @Override + public String modelFileFolder() { + return outputFolder + File.separator + sourceFolder + File.separator + modelPackage(); + } + + @Override + public String escapeReservedWord(String 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(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 " + 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 toModelFilename(String name) { + // should be the same as the model name + return "New-" + toModelName(name); + } + + /** + * 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 -> Long based on hashes in this type's constructor + if (typeMapping.containsKey(openAPIType)) { + type = typeMapping.get(openAPIType); + if (languageSpecificPrimitives.contains(type)) { + return type; + } + } else { + type = openAPIType; + } + + // model/object + return toModelName(type); + } + + /** + * Output the type declaration of the property + * + * @param p OpenAPI Schema object + * @return a string presentation of the property type + */ + @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(); + // TODO not sure if the following map/hash declaration is correct + return "{String, " + getTypeDeclaration(inner) + "}"; + } else if (!languageSpecificPrimitives.contains(getSchemaType(p))) { + return packageName + ".Model." + super.getTypeDeclaration(p); + } + return super.getTypeDeclaration(p); + } + + @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 Map postProcessOperations(Map objs) { + Map operations = (Map) objs.get("operations"); + List operationList = (List) operations.get("operation"); + for (CodegenOperation op : operationList) { + int index = 0; + for (CodegenParameter p : op.allParams) { + p.vendorExtensions.put("x-index", index); + index++; + } + } + return objs; + } +} 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 new file mode 100755 index 00000000000..b37aae3c608 --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonClientCodegen.java @@ -0,0 +1,674 @@ +package org.openapitools.codegen.languages; + +import org.openapitools.codegen.CliOption; +import org.openapitools.codegen.CodegenConfig; +import org.openapitools.codegen.CodegenConstants; +import org.openapitools.codegen.CodegenModel; +import org.openapitools.codegen.CodegenParameter; +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 java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +import org.apache.commons.lang3.StringUtils; + +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; + +public class PythonClientCodegen extends DefaultCodegen implements CodegenConfig { + public static final String PACKAGE_URL = "packageUrl"; + public static final String DEFAULT_LIBRARY = "urllib3"; + + protected String packageName; // e.g. petstore_api + protected String packageVersion; + protected String projectName; // for setup.py, e.g. petstore-api + protected String packageUrl; + protected String apiDocPath = "docs/"; + protected String modelDocPath = "docs/"; + + protected Map regexModifiers; + + private String testFolder; + + public PythonClientCodegen() { + super(); + + // clear import mapping (from default generator) as python does not use it + // at the moment + importMapping.clear(); + + supportsInheritance = true; + modelPackage = "models"; + apiPackage = "api"; + outputFolder = "generated-code" + File.separatorChar + "python"; + + modelTemplateFiles.put("model.mustache", ".py"); + apiTemplateFiles.put("api.mustache", ".py"); + + modelTestTemplateFiles.put("model_test.mustache", ".py"); + apiTestTemplateFiles.put("api_test.mustache", ".py"); + + embeddedTemplateDir = templateDir = "python"; + + modelDocTemplateFiles.put("model_doc.mustache", ".md"); + apiDocTemplateFiles.put("api_doc.mustache", ".md"); + + testFolder = "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("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"); + // TODO binary should be mapped to byte array + // mapped to String as a workaround + typeMapping.put("binary", "str"); + typeMapping.put("ByteArray", "str"); + // map uuid to string for the time being + typeMapping.put("UUID", "str"); + + // 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")); + + 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.clear(); + cliOptions.add(new CliOption(CodegenConstants.PACKAGE_NAME, "python package name (convention: snake_case).") + .defaultValue("swagger_client")); + cliOptions.add(new CliOption(CodegenConstants.PROJECT_NAME, "python project name in setup.py (e.g. petstore-api).")); + cliOptions.add(new CliOption(CodegenConstants.PACKAGE_VERSION, "python package version.") + .defaultValue("1.0.0")); + cliOptions.add(new CliOption(PACKAGE_URL, "python package URL.")); + cliOptions.add(CliOption.newBoolean(CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG, + CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG_DESC).defaultValue(Boolean.TRUE.toString())); + cliOptions.add(new CliOption(CodegenConstants.HIDE_GENERATION_TIMESTAMP, "hides the timestamp when files were generated") + .defaultValue(Boolean.TRUE.toString())); + + supportedLibraries.put("urllib3", "urllib3-based client"); + supportedLibraries.put("asyncio", "Asyncio-based client (python 3.5+)"); + supportedLibraries.put("tornado", "tornado-based client"); + CliOption libraryOption = new CliOption(CodegenConstants.LIBRARY, "library template (sub-template) to use"); + libraryOption.setDefault(DEFAULT_LIBRARY); + cliOptions.add(libraryOption); + setLibrary(DEFAULT_LIBRARY); + } + + @Override + public void processOpts() { + super.processOpts(); + Boolean excludeTests = false; + + 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 { + setPackageName("swagger_client"); + } + + if (additionalProperties.containsKey(CodegenConstants.PROJECT_NAME)) { + setProjectName((String) additionalProperties.get(CodegenConstants.PROJECT_NAME)); + } + else { + // default: set project based on package name + // e.g. petstore_api (package name) => petstore-api (project name) + setProjectName(packageName.replaceAll("_", "-")); + } + + if (additionalProperties.containsKey(CodegenConstants.PACKAGE_VERSION)) { + setPackageVersion((String) additionalProperties.get(CodegenConstants.PACKAGE_VERSION)); + } + else { + setPackageVersion("1.0.0"); + } + + // 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())); + } + + additionalProperties.put(CodegenConstants.PROJECT_NAME, projectName); + additionalProperties.put(CodegenConstants.PACKAGE_NAME, packageName); + additionalProperties.put(CodegenConstants.PACKAGE_VERSION, packageVersion); + + // make api and model doc path available in mustache template + additionalProperties.put("apiDocPath", apiDocPath); + additionalProperties.put("modelDocPath", modelDocPath); + + if (additionalProperties.containsKey(PACKAGE_URL)) { + setPackageUrl((String) additionalProperties.get(PACKAGE_URL)); + } + + supportingFiles.add(new SupportingFile("README.mustache", "", "README.md")); + + 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("configuration.mustache", packageName, "configuration.py")); + supportingFiles.add(new SupportingFile("__init__package.mustache", packageName, "__init__.py")); + 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)) { + supportingFiles.add(new SupportingFile("__init__test.mustache", testFolder, "__init__.py")); + } + 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("setup.mustache", "", "setup.py")); + supportingFiles.add(new SupportingFile("api_client.mustache", packageName, "api_client.py")); + + if ("asyncio".equals(getLibrary())) { + supportingFiles.add(new SupportingFile("asyncio/rest.mustache", packageName, "rest.py")); + additionalProperties.put("asyncio", "true"); + } else if ("tornado".equals(getLibrary())) { + supportingFiles.add(new SupportingFile("tornado/rest.mustache", packageName, "rest.py")); + additionalProperties.put("tornado", "true"); + } else { + supportingFiles.add(new SupportingFile("rest.mustache", packageName, "rest.py")); + } + + modelPackage = packageName + "." + modelPackage; + apiPackage = packageName + "." + apiPackage; + + } + + private static String dropDots(String str) { + return str.replaceAll("\\.", "_"); + } + + @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 Map postProcessModels(Map objs) { + // process enum in models + return postProcessModelsEnum(objs); + } + + @Override + public void postProcessParameter(CodegenParameter parameter){ + postProcessPattern(parameter.pattern, parameter.vendorExtensions); + } + + @Override + public void postProcessModelProperty(CodegenModel model, CodegenProperty property) { + postProcessPattern(property.pattern, property.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); + } + } + + @Override + public CodegenType getTag() { + return CodegenType.CLIENT; + } + + @Override + public String getName() { + return "python"; + } + + @Override + public String getHelp() { + return "Generates a Python client library."; + } + + @Override + public String escapeReservedWord(String name) { + if(this.reservedWordsMappings().containsKey(name)) { + return this.reservedWordsMappings().get(name); + } + return "_" + name; + } + + @Override + public String apiDocFileFolder() { + return (outputFolder + "/" + apiDocPath); + } + + @Override + public String modelDocFileFolder() { + return (outputFolder + "/" + modelDocPath); + } + + @Override + public String toModelDocFilename(String name) { + return toModelName(name); + } + + @Override + public String toApiDocFilename(String name) { + return toApiName(name); + } + + + @Override + public String apiFileFolder() { + return outputFolder + File.separatorChar + apiPackage().replace('.', File.separatorChar); + } + + @Override + public String modelFileFolder() { + return outputFolder + File.separatorChar + modelPackage().replace('.', File.separatorChar); + } + + @Override + public String apiTestFileFolder() { + return outputFolder + File.separatorChar + testFolder; + } + + @Override + public String modelTestFileFolder() { + return outputFolder + File.separatorChar + testFolder; + } + + @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 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) { + // to avoid conflicts with 'callback' parameter for async call + if ("callback".equals(name)) { + return "param_callback"; + } + + // 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'. + // 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 toModelFilename(String name) { + // underscore the model file name + // PhoneNumber => phone_number + return underscore(dropDots(toModelName(name))); + } + + @Override + public String toModelTestFilename(String name) { + return "test_" + toModelFilename(name); + } + + @Override + public String toApiFilename(String name) { + // replace - with _ e.g. created-at => created_at + name = name.replaceAll("-", "_"); + + // e.g. PhoneNumberApi.py => phone_number_api.py + return underscore(name) + "_api"; + } + + @Override + public String toApiTestFilename(String name) { + return "test_" + toApiFilename(name); + } + + @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 toApiVarName(String name) { + if (name.length() == 0) { + return "default_api"; + } + return underscore(name) + "_api"; + } + + @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)); + } + + public void setPackageName(String packageName) { + this.packageName = packageName; + } + + public void setProjectName(String projectName) { + this.projectName= projectName; + } + + public void setPackageVersion(String packageVersion) { + this.packageVersion = packageVersion; + } + + public void setPackageUrl(String packageUrl) { + this.packageUrl = packageUrl; + } + + /** + * 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. + * + * @param packageName Package name + * @return Python package name that conforms to PEP 0008 + */ + @SuppressWarnings("static-method") + public String generatePackageName(String packageName) { + return underscore(packageName.replaceAll("[^\\w]+", "")); + } + + /** + * 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) { + if (Pattern.compile("\r\n|\r|\n").matcher(dp.getDefault()).find()) + return "'''" + dp.getDefault() + "'''"; + else + 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 (example == null) { + example = "56"; + } + } else if ("Float".equalsIgnoreCase(type) || "Double".equalsIgnoreCase(type)) { + if (example == null) { + example = "3.4"; + } + } else if ("BOOLEAN".equalsIgnoreCase(type) || "bool".equalsIgnoreCase(type)) { + if (example == null) { + example = "True"; + } + } else if ("file".equalsIgnoreCase(type)) { + if (example == null) { + example = "/path/to/file"; + } + example = "'" + escapeText(example) + "'"; + } 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 = this.packageName + "." + type + "()"; + } else { + LOGGER.warn("Type " + type + " not handled properly in setParameterExampleValue"); + } + + 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; + } + + @Override + public String sanitizeTag(String tag) { + return sanitizeName(tag); + } + + @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("'''", "'_'_'"); + } + +} diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RClientCodegen.java new file mode 100644 index 00000000000..726ad5f43e5 --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RClientCodegen.java @@ -0,0 +1,453 @@ +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 RClientCodegen extends DefaultCodegen implements CodegenConfig { + static Logger LOGGER = LoggerFactory.getLogger(RClientCodegen.class); + + protected String packageName = "swagger"; + protected String packageVersion = "1.0.0"; + protected String apiDocPath = "docs/"; + protected String modelDocPath = "docs/"; + + public CodegenType getTag() { + return CodegenType.CLIENT; + } + + public String getName() { + return "r"; + } + + public String getHelp() { + return "Generates a R client library (beta)."; + } + + public RClientCodegen() { + super(); + outputFolder = "generated-code/r"; + modelTemplateFiles.put("model.mustache", ".r"); + apiTemplateFiles.put("api.mustache", ".r"); + + modelDocTemplateFiles.put("model_doc.mustache", ".md"); + apiDocTemplateFiles.put("api_doc.mustache", ".md"); + + embeddedTemplateDir = templateDir = "r"; + + setReservedWordsLowerCase( + Arrays.asList( + // reserved words: https://stat.ethz.ch/R-manual/R-devel/library/base/html/Reserved.html + "if", "else", "repeat", "while", "function", "for", "in", + "next", "break", "TRUE", "FALSE", "NULL", "Inf", "NaN", + "NA", "NA_integer_", "NA_real_", "NA_complex_", "NA_character_" + ) + ); + + defaultIncludes = new HashSet( + Arrays.asList( + "map", + "array") + ); + + languageSpecificPrimitives = new HashSet( + Arrays.asList( + "Integer", + "Numeric", + "Character") + ); + + instantiationTypes.clear(); + + typeMapping.clear(); + typeMapping.put("integer", "Integer"); + typeMapping.put("long", "Integer"); + typeMapping.put("number", "Numeric"); + typeMapping.put("float", "Numeric"); + typeMapping.put("double", "Numeric"); + typeMapping.put("boolean", "Character"); + typeMapping.put("string", "Character"); + typeMapping.put("UUID", "Character"); + typeMapping.put("date", "Character"); + typeMapping.put("DateTime", "Character"); + typeMapping.put("password", "Character"); + typeMapping.put("file", "TODO_FILE_MAPPING"); + // map binary to string as a workaround + // the correct solution is to use []byte + typeMapping.put("binary", "Character"); + typeMapping.put("ByteArray", "Character"); + typeMapping.put("object", "TODO_OBJECT_MAPPING"); + + cliOptions.clear(); + cliOptions.add(new CliOption(CodegenConstants.PACKAGE_NAME, "R package name (convention: lowercase).") + .defaultValue("swagger")); + cliOptions.add(new CliOption(CodegenConstants.PACKAGE_VERSION, "R package version.") + .defaultValue("1.0.0")); + cliOptions.add(new CliOption(CodegenConstants.HIDE_GENERATION_TIMESTAMP, "hides the timestamp when files were generated") + .defaultValue(Boolean.TRUE.toString())); + + } + + @Override + public 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); + + apiTestTemplateFiles.clear(); // TODO: add api test template + modelTestTemplateFiles.clear(); // TODO: add model test template + + 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("description.mustache", "", "DESCRIPTION")); + supportingFiles.add(new SupportingFile("Rbuildignore.mustache", "", ".Rbuildignore")); + supportingFiles.add(new SupportingFile(".travis.yml", "", ".travis.yml")); + supportingFiles.add(new SupportingFile("response.mustache", "/R", "Response.r")); + supportingFiles.add(new SupportingFile("element.mustache", "/R", "Element.r")); + supportingFiles.add(new SupportingFile("api_client.mustache", "/R", "ApiClient.r")); + supportingFiles.add(new SupportingFile("NAMESPACE.mustache", "", "NAMESPACE")); + } + + @Override + public String escapeReservedWord(String name) + { + // Can't start with an underscore, as our fields need to start with an + // UppercaseLetter so that R 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 + "R" + File.separator; + } + + public String modelFileFolder() { + return outputFolder + File.separator + "R" + 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 " + 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) + } + + return camelize(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.r => pet_api.r + return camelize(name + "_api"); + } + + @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 camelize(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 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 " + underscore("call_" + operationId)); + sanitizedOperationId = "call_" + sanitizedOperationId; + } + + return underscore(sanitizedOperationId); + } + + @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; + } + + @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; + } + } +} 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/Rails5ServerCodegen.java new file mode 100644 index 00000000000..abcf121c3e7 --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Rails5ServerCodegen.java @@ -0,0 +1,339 @@ +package org.openapitools.codegen.languages; + +import java.text.SimpleDateFormat; +import java.util.Date; +import com.fasterxml.jackson.core.JsonProcessingException; + +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.core.util.Yaml; + +import java.io.File; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Rails5ServerCodegen extends DefaultCodegen implements CodegenConfig { + + private static final Logger LOGGER = LoggerFactory.getLogger(Rails5ServerCodegen.class); + private static final SimpleDateFormat MIGRATE_FILE_NAME_FORMAT = new SimpleDateFormat("yyyyMMddHHmmss"); + + protected String gemName; + protected String moduleName; + protected String gemVersion = "1.0.0"; + protected String appFolder = "app"; + protected String channelsFolder = appFolder + File.separator + "channels"; + protected String applicationCableFolder = channelsFolder + File.separator + "application_cable"; + protected String controllersFolder = appFolder + File.separator + "controllers"; + protected String jobsFolder = appFolder + File.separator + "jobs"; + protected String mailersFolder = appFolder + File.separator + "mailers"; + protected String modelsFolder = appFolder + File.separator + "models"; + protected String viewsFolder = appFolder + File.separator + "views"; + protected String layoutsFolder = viewsFolder + File.separator + "layouts"; + protected String binFolder = "bin"; + protected String configFolder = "config"; + protected String environmentsFolder = configFolder + File.separator + "environments"; + protected String initializersFolder = configFolder + File.separator + "initializers"; + protected String localesFolder = configFolder + File.separator + "locales"; + protected String dbFolder = "db"; + protected String migrateFolder = dbFolder + File.separator + "migrate"; + protected String libFolder = "lib"; + protected String tasksFolder = libFolder + File.separator + "tasks"; + protected String logFolder = "log"; + protected String publicFolder = "public"; + protected String testFolder = "test"; + protected String tmpFolder = "tmp"; + protected String cacheFolder = tmpFolder + File.separator + "cache"; + protected String pidFolder = tmpFolder + File.separator + "pids"; + protected String socketsFolder = tmpFolder + File.separator + "sockets"; + protected String vendorFolder = "vendor"; + + public Rails5ServerCodegen() { + super(); + outputFolder = "generated-code" + File.separator + "rails5"; + apiPackage = "app/controllers"; + apiTemplateFiles.put("controller.mustache", ".rb"); + + modelPackage = "app/models"; + modelTemplateFiles.put("model.mustache", ".rb"); + + embeddedTemplateDir = templateDir = "rails5"; + + typeMapping.clear(); + languageSpecificPrimitives.clear(); + + setReservedWordsLowerCase( + Arrays.asList( + "__FILE__", "and", "def", "end", "in", "or", "self", "unless", "__LINE__", + "begin", "defined?", "ensure", "module", "redo", "super", "until", "BEGIN", + "break", "do", "false", "next", "rescue", "then", "when", "END", "case", + "else", "for", "nil", "retry", "true", "while", "alias", "class", "elsif", + "if", "not", "return", "undef", "yield") + ); + + typeMapping.put("string", "string"); + typeMapping.put("char", "string"); + typeMapping.put("int", "integer"); + typeMapping.put("integer", "integer"); + typeMapping.put("long", "integer"); + typeMapping.put("short", "integer"); + typeMapping.put("float", "float"); + typeMapping.put("double", "decimal"); + typeMapping.put("number", "float"); + typeMapping.put("date", "date"); + typeMapping.put("DateTime", "datetime"); + typeMapping.put("boolean", "boolean"); + typeMapping.put("binary", "string"); + typeMapping.put("ByteArray", "string"); + typeMapping.put("UUID", "string"); + + // remove modelPackage and apiPackage added by default + cliOptions.clear(); + } + + @Override + public void processOpts() { + super.processOpts(); + + // use constant model/api package (folder path) + //setModelPackage("models"); + setApiPackage("app/controllers"); + + supportingFiles.add(new SupportingFile("Gemfile", "", "Gemfile")); + supportingFiles.add(new SupportingFile("README.md", "", "README.md")); + supportingFiles.add(new SupportingFile("Rakefile", "", "Rakefile")); + supportingFiles.add(new SupportingFile("config.ru", "", "config.ru")); + supportingFiles.add(new SupportingFile("channel.rb", applicationCableFolder, "channel.rb")); + supportingFiles.add(new SupportingFile("connection.rb", applicationCableFolder, "connection.rb")); + supportingFiles.add(new SupportingFile("application_controller.rb", controllersFolder, "application_controller.rb")); + supportingFiles.add(new SupportingFile("application_job.rb", jobsFolder, "application_job.rb")); + supportingFiles.add(new SupportingFile("application_mailer.rb", mailersFolder, "application_mailer.rb")); + supportingFiles.add(new SupportingFile("application_record.rb", modelsFolder, "application_record.rb")); + supportingFiles.add(new SupportingFile("mailer.html.erb", layoutsFolder, "mailer.html.erb")); + supportingFiles.add(new SupportingFile("mailer.text.erb", layoutsFolder, "mailer.text.erb")); + supportingFiles.add(new SupportingFile("bundle", binFolder, "bundle")); + supportingFiles.add(new SupportingFile("rails", binFolder, "rails")); + supportingFiles.add(new SupportingFile("rake", binFolder, "rake")); + supportingFiles.add(new SupportingFile("setup", binFolder, "setup")); + supportingFiles.add(new SupportingFile("update", binFolder, "update")); + supportingFiles.add(new SupportingFile("development.rb", environmentsFolder, "development.rb")); + supportingFiles.add(new SupportingFile("production.rb", environmentsFolder, "production.rb")); + supportingFiles.add(new SupportingFile("active_record_belongs_to_required_by_default.rb", initializersFolder, "active_record_belongs_to_required_by_default.rb")); + supportingFiles.add(new SupportingFile("application_controller_renderer.rb", initializersFolder, "application_controller_renderer.rb")); + supportingFiles.add(new SupportingFile("backtrace_silencers.rb", initializersFolder, "backtrace_silencers.rb")); + supportingFiles.add(new SupportingFile("callback_terminator.rb", initializersFolder, "callback_terminator.rb")); + supportingFiles.add(new SupportingFile("cors.rb", initializersFolder, "cors.rb")); + supportingFiles.add(new SupportingFile("filter_parameter_logging.rb", initializersFolder, "filter_parameter_logging.rb")); + supportingFiles.add(new SupportingFile("inflections.rb", initializersFolder, "inflections.rb")); + supportingFiles.add(new SupportingFile("mime_types.rb", initializersFolder, "mime_types.rb")); + supportingFiles.add(new SupportingFile("ssl_options.rb", initializersFolder, "ssl_options.rb")); + supportingFiles.add(new SupportingFile("to_time_preserves_timezone.rb", initializersFolder, "to_time_preserves_timezone.rb")); + supportingFiles.add(new SupportingFile("en.yml", localesFolder, "en.yml")); + supportingFiles.add(new SupportingFile("application.rb", configFolder, "application.rb")); + supportingFiles.add(new SupportingFile("boot.rb", configFolder, "boot.rb")); + supportingFiles.add(new SupportingFile("cable.yml", configFolder, "cable.yml")); + supportingFiles.add(new SupportingFile("database.yml", configFolder, "database.yml")); + supportingFiles.add(new SupportingFile("environment.rb", configFolder, "environment.rb")); + supportingFiles.add(new SupportingFile("puma.rb", configFolder, "puma.rb")); + supportingFiles.add(new SupportingFile("routes.mustache", configFolder, "routes.rb")); + supportingFiles.add(new SupportingFile("secrets.yml", configFolder, "secrets.yml")); + supportingFiles.add(new SupportingFile("spring.rb", configFolder, "spring.rb")); + supportingFiles.add(new SupportingFile(".keep", migrateFolder, ".keep")); + supportingFiles.add(new SupportingFile("migrate.mustache", migrateFolder, "0_init_tables.rb")); + supportingFiles.add(new SupportingFile("schema.rb", dbFolder, "schema.rb")); + supportingFiles.add(new SupportingFile("seeds.rb", dbFolder, "seeds.rb")); + supportingFiles.add(new SupportingFile(".keep", tasksFolder, ".keep")); + supportingFiles.add(new SupportingFile(".keep", logFolder, ".keep")); + supportingFiles.add(new SupportingFile("404.html", publicFolder, "404.html")); + supportingFiles.add(new SupportingFile("422.html", publicFolder, "422.html")); + supportingFiles.add(new SupportingFile("500.html", publicFolder, "500.html")); + supportingFiles.add(new SupportingFile("apple-touch-icon-precomposed.png", publicFolder, "apple-touch-icon-precomposed.png")); + supportingFiles.add(new SupportingFile("apple-touch-icon.png", publicFolder, "apple-touch-icon.png")); + supportingFiles.add(new SupportingFile("favicon.ico", publicFolder, "favicon.ico")); + supportingFiles.add(new SupportingFile("robots.txt", publicFolder, "robots.txt")); + supportingFiles.add(new SupportingFile("robots.txt", publicFolder, "robots.txt")); + supportingFiles.add(new SupportingFile("test_helper.rb", testFolder, "test_helper.rb")); + supportingFiles.add(new SupportingFile(".keep", cacheFolder, ".keep")); + supportingFiles.add(new SupportingFile(".keep", pidFolder, ".keep")); + supportingFiles.add(new SupportingFile(".keep", socketsFolder, ".keep")); + supportingFiles.add(new SupportingFile("restart.txt", tmpFolder, "restart.txt")); + supportingFiles.add(new SupportingFile(".keep", vendorFolder, ".keep")); + } + + @Override + public CodegenType getTag() { + return CodegenType.SERVER; + } + + @Override + public String getName() { + return "rails5"; + } + + @Override + public String getHelp() { + return "Generates a Rails5 server 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 + File.separator + apiPackage.replace("/", File.separator); + } + + @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 toDefaultValue(Schema p) { + return "null"; + } + + @Override + public String toVarName(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'. + + // 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 getSchemaType(Schema p) { + String swaggerType = super.getSchemaType(p); + String type = null; + if (typeMapping.containsKey(swaggerType)) { + return typeMapping.get(swaggerType); + } + return "string"; + } + + @Override + public String toParamName(String name) { + // should be the same as variable name + return toVarName(name); + } + + @Override + public String toModelName(String name) { + // 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; + } + + // camelize the model name + // phone_number => PhoneNumber + return camelize(name); + } + + @Override + public String toModelFilename(String name) { + // model name cannot use reserved keyword, e.g. return + if (isReservedWord(name)) { + String filename = underscore("model_" + name); + LOGGER.warn(name + " (reserved word) cannot be used as model filename. Renamed to " + filename); + return filename; + } + + // underscore the model file name + // PhoneNumber.rb => phone_number.rb + 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. DefaultController => defaults_controller.rb + return underscore(name) + "_controller"; + } + + @Override + public String toApiName(String name) { + if (name.length() == 0) { + return "ApiController"; + } + // e.g. phone_number_api => PhoneNumberApi + return camelize(name) + "Controller"; + } + + @Override + public String toOperationId(String operationId) { + // method name cannot use reserved keyword, e.g. return + if (isReservedWord(operationId)) { + String newOperationId = underscore("call_" + operationId); + LOGGER.warn(operationId + " (reserved word) cannot be used as method name. Renamed to " + newOperationId); + return newOperationId; + } + + return underscore(operationId); + } + + @Override + public Map postProcessSupportingFileData(Map objs) { + OpenAPI openAPI = (OpenAPI) objs.get("swagger"); + 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 + public String escapeQuotationMark(String input) { + // remove ' to avoid code injection + return input.replace("'", ""); + } + + @Override + public String escapeUnsafeCharacters(String input) { + return input.replace("=end", "=_end").replace("=begin", "=_begin"); + } +} 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/SilexServerCodegen.java new file mode 100644 index 00000000000..aad5a466f01 --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SilexServerCodegen.java @@ -0,0 +1,246 @@ +package org.openapitools.codegen.languages; + +import org.openapitools.codegen.CodegenConfig; +import org.openapitools.codegen.CodegenConstants; +import org.openapitools.codegen.CodegenType; +import org.openapitools.codegen.CodegenOperation; +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 java.io.File; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; + +public class SilexServerCodegen 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() { + super(); + + invokerPackage = camelize("SwaggerServer"); + + String packagePath = "SwaggerServer"; + + modelPackage = packagePath + "/lib/models"; + apiPackage = packagePath + "/lib"; + outputFolder = "generated-code/php-silex"; + + // no model, api files + modelTemplateFiles.clear(); + apiTemplateFiles.clear(); + + embeddedTemplateDir = templateDir = "php-silex"; + + setReservedWordsLowerCase( + Arrays.asList( + "__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") + ); + + additionalProperties.put(CodegenConstants.INVOKER_PACKAGE, invokerPackage); + additionalProperties.put(CodegenConstants.GROUP_ID, groupId); + additionalProperties.put(CodegenConstants.ARTIFACT_ID, artifactId); + additionalProperties.put(CodegenConstants.ARTIFACT_VERSION, artifactVersion); + + // ref: http://php.net/manual/en/language.types.intro.php + languageSpecificPrimitives = new HashSet( + Arrays.asList( + "boolean", + "int", + "integer", + "double", + "float", + "string", + "object", + "DateTime", + "mixed", + "number") + ); + + instantiationTypes.put("array", "array"); + instantiationTypes.put("map", "map"); + + // 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("float", "float"); + typeMapping.put("double", "double"); + typeMapping.put("string", "string"); + typeMapping.put("byte", "int"); + typeMapping.put("boolean", "boolean"); + typeMapping.put("date", "DateTime"); + typeMapping.put("datetime", "DateTime"); + typeMapping.put("file", "string"); + typeMapping.put("map", "map"); + typeMapping.put("array", "array"); + typeMapping.put("list", "array"); + typeMapping.put("object", "object"); + //TODO binary should be mapped to byte array + // mapped to String as a workaround + typeMapping.put("binary", "string"); + + supportingFiles.add(new SupportingFile("README.mustache", packagePath.replace('/', File.separatorChar), "README.md")); + supportingFiles.add(new SupportingFile("composer.json", packagePath.replace('/', File.separatorChar), "composer.json")); + supportingFiles.add(new SupportingFile("index.mustache", packagePath.replace('/', File.separatorChar), "index.php")); + supportingFiles.add(new SupportingFile(".htaccess", packagePath.replace('/', File.separatorChar), ".htaccess")); + } + + @Override + public CodegenType getTag() { + return CodegenType.SERVER; + } + + @Override + public String getName() { + return "php-silex"; + } + + @Override + public String getHelp() { + return "Generates a PHP Silex server 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 + "/" + apiPackage()).replace('/', File.separatorChar); + } + + @Override + public String modelFileFolder() { + return (outputFolder + "/" + 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 type; + } else if (instantiationTypes.containsKey(type)) { + return type; + } + } else { + type = swaggerType; + } + if (type == null) { + return null; + } + return toModelName(type); + } + + @Override + public String toDefaultValue(Schema p) { + 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) { + // model name cannot use reserved keyword + if (isReservedWord(name)) { + escapeReservedWord(name); // e.g. return => _return + } + + // 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 escapeQuotationMark(String input) { + // remove ' to avoid code injection + return input.replace("'", ""); + } + + @Override + public String escapeUnsafeCharacters(String input) { + return input.replace("*/", "*_/").replace("/*", "/_*"); + } + + @Override + public Map postProcessOperations(Map objs) { + Map operations = (Map) objs.get("operations"); + List operationList = (List) operations.get("operation"); + for (CodegenOperation op : operationList) { + String path = new String(op.path); + String[] items = path.split("/", -1); + String opsPath = ""; + int pathParamIndex = 0; + + 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) + "}"; + } + } + + op.path = StringUtils.join(items, "/"); + } + + return objs; + } + +} diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SinatraServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SinatraServerCodegen.java new file mode 100644 index 00000000000..9dfdb62828e --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SinatraServerCodegen.java @@ -0,0 +1,264 @@ +package org.openapitools.codegen.languages; + +import com.fasterxml.jackson.core.JsonProcessingException; + +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.core.util.Yaml; + +import java.io.File; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SinatraServerCodegen extends DefaultCodegen implements CodegenConfig { + + private static final Logger LOGGER = LoggerFactory.getLogger(SinatraServerCodegen.class); + + protected String gemName; + protected String moduleName; + protected String gemVersion = "1.0.0"; + protected String libFolder = "lib"; + + public SinatraServerCodegen() { + super(); + apiPackage = "lib"; + outputFolder = "generated-code" + File.separator + "sinatra"; + + // no model + modelTemplateFiles.clear(); + apiTemplateFiles.put("api.mustache", ".rb"); + embeddedTemplateDir = templateDir = "sinatra"; + + typeMapping.clear(); + languageSpecificPrimitives.clear(); + + setReservedWordsLowerCase( + Arrays.asList( + "__FILE__", "and", "def", "end", "in", "or", "self", "unless", "__LINE__", + "begin", "defined?", "ensure", "module", "redo", "super", "until", "BEGIN", + "break", "do", "false", "next", "rescue", "then", "when", "END", "case", + "else", "for", "nil", "retry", "true", "while", "alias", "class", "elsif", + "if", "not", "return", "undef", "yield") + ); + + languageSpecificPrimitives.add("int"); + languageSpecificPrimitives.add("array"); + languageSpecificPrimitives.add("map"); + languageSpecificPrimitives.add("string"); + languageSpecificPrimitives.add("DateTime"); + + typeMapping.put("long", "int"); + typeMapping.put("integer", "int"); + typeMapping.put("Array", "array"); + typeMapping.put("String", "string"); + typeMapping.put("List", "array"); + typeMapping.put("map", "map"); + //TODO binary should be mapped to byte array + // mapped to String as a workaround + typeMapping.put("binary", "string"); + typeMapping.put("UUID", "string"); + + // remove modelPackage and apiPackage added by default + cliOptions.clear(); + } + + @Override + public void processOpts() { + super.processOpts(); + + // use constant model/api package (folder path) + //setModelPackage("models"); + setApiPackage("api"); + + supportingFiles.add(new SupportingFile("my_app.mustache", "", "my_app.rb")); + supportingFiles.add(new SupportingFile("Swaggering.rb", libFolder, "swaggering.rb")); + supportingFiles.add(new SupportingFile("config.ru", "", "config.ru")); + supportingFiles.add(new SupportingFile("Gemfile", "", "Gemfile")); + supportingFiles.add(new SupportingFile("README.md", "", "README.md")); + supportingFiles.add(new SupportingFile("swagger.mustache","","swagger.yaml")); + } + + @Override + public CodegenType getTag() { + return CodegenType.SERVER; + } + + @Override + public String getName() { + return "ruby-sinatra"; + } + + @Override + public String getHelp() { + return "Generates a Ruby Sinatra server 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 + File.separator + apiPackage.replace("/", File.separator); + } + + @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 openAPIType = super.getSchemaType(p); + String type = null; + if (typeMapping.containsKey(openAPIType)) { + type = typeMapping.get(openAPIType); + if (languageSpecificPrimitives.contains(type)) { + return type; + } + } else { + type = openAPIType; + } + if (type == null) { + return null; + } + return type; + } + + @Override + public String toDefaultValue(Schema p) { + return "null"; + } + + @Override + public String toVarName(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'. + + // 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) { + // should be the same as variable name + return toVarName(name); + } + + @Override + public String toModelName(String name) { + // model name cannot use reserved keyword, e.g. return + if (isReservedWord(name)) { + LOGGER.warn(name + " (reserved word) cannot be used as model filename. Renamed to " + camelize("model_" + name)); + name = "model_" + name; // e.g. return => ModelReturn (after camelize) + } + + // camelize the model name + // phone_number => PhoneNumber + return camelize(name); + } + + @Override + public String toModelFilename(String name) { + // model name cannot use reserved keyword, e.g. return + if (isReservedWord(name)) { + LOGGER.warn(name + " (reserved word) cannot be used as model filename. Renamed to " + underscore("model_" + name)); + name = "model_" + name; // e.g. return => ModelReturn (after camelize) + } + + // underscore the model file name + // PhoneNumber.rb => phone_number.rb + 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. PhoneNumberApi.rb => phone_number_api.rb + return underscore(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) { + // method name cannot use reserved keyword, e.g. return + if (isReservedWord(operationId)) { + String newOperationId = underscore("call_" + operationId); + LOGGER.warn(operationId + " (reserved word) cannot be used as method name. Renamed to " + newOperationId); + return newOperationId; + } + + return underscore(operationId); + } + + @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 + public String escapeQuotationMark(String input) { + // remove ' to avoid code injection + return input.replace("'", ""); + } + + @Override + public String escapeUnsafeCharacters(String input) { + return input.replace("=end", "=_end").replace("=begin", "=_begin"); + } +} 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/SlimFrameworkServerCodegen.java new file mode 100644 index 00000000000..0d7e38eaed8 --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SlimFrameworkServerCodegen.java @@ -0,0 +1,286 @@ +package org.openapitools.codegen.languages; + +import org.apache.commons.lang3.StringUtils; +import org.openapitools.codegen.CodegenConfig; +import org.openapitools.codegen.CodegenConstants; +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.core.util.Yaml; + +import java.io.File; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.regex.Matcher; + +public class SlimFrameworkServerCodegen extends DefaultCodegen implements CodegenConfig { + protected String invokerPackage; + protected String srcBasePath = "lib"; + protected String groupId = "io.swagger"; + protected String artifactId = "swagger-server"; + protected String artifactVersion = "1.0.0"; + protected String packagePath = ""; // empty packagePath (top folder) + + + private String variableNamingConvention = "camelCase"; + + public SlimFrameworkServerCodegen() { + super(); + + // clear import mapping (from default generator) as slim does not use it + // at the moment + importMapping.clear(); + + invokerPackage = camelize("SwaggerServer"); + + //String packagePath = "SwaggerServer"; + + modelPackage = packagePath + "\\Models"; + apiPackage = packagePath; + outputFolder = "generated-code" + File.separator + "slim"; + modelTemplateFiles.put("model.mustache", ".php"); + + // no api files + apiTemplateFiles.clear(); + + embeddedTemplateDir = templateDir = "slim"; + + setReservedWordsLowerCase( + Arrays.asList( + "__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") + ); + + additionalProperties.put(CodegenConstants.INVOKER_PACKAGE, invokerPackage); + additionalProperties.put(CodegenConstants.GROUP_ID, groupId); + additionalProperties.put(CodegenConstants.ARTIFACT_ID, artifactId); + additionalProperties.put(CodegenConstants.ARTIFACT_VERSION, artifactVersion); + + // ref: http://php.net/manual/en/language.types.intro.php + languageSpecificPrimitives = new HashSet( + Arrays.asList( + "boolean", + "int", + "integer", + "double", + "float", + "string", + "object", + "DateTime", + "mixed", + "number") + ); + + instantiationTypes.put("array", "array"); + instantiationTypes.put("map", "map"); + + // 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("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", "\\SplFileObject"); + typeMapping.put("map", "map"); + typeMapping.put("array", "array"); + typeMapping.put("list", "array"); + typeMapping.put("object", "object"); + //TODO binary should be mapped to byte array + // mapped to String as a workaround + typeMapping.put("binary", "string"); + + supportingFiles.add(new SupportingFile("README.mustache", packagePath.replace('/', File.separatorChar), "README.md")); + supportingFiles.add(new SupportingFile("composer.json", packagePath.replace('/', File.separatorChar), "composer.json")); + supportingFiles.add(new SupportingFile("index.mustache", packagePath.replace('/', File.separatorChar), "index.php")); + supportingFiles.add(new SupportingFile(".htaccess", packagePath.replace('/', File.separatorChar), ".htaccess")); + } + + @Override + public CodegenType getTag() { + return CodegenType.SERVER; + } + + @Override + public String getName() { + return "php-slim"; + } + + @Override + public String getHelp() { + return "Generates a PHP Slim Framework server 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 + "/" + toPackagePath(apiPackage, srcBasePath)); + } + + @Override + public String modelFileFolder() { + return (outputFolder + "/" + toPackagePath(modelPackage, srcBasePath)); + } + + @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) + "]"; + } else if (!StringUtils.isEmpty(p.get$ref())) { + String type = super.getTypeDeclaration(p); + return (!languageSpecificPrimitives.contains(type)) + ? "\\" + modelPackage + "\\" + type : type; + } + return super.getTypeDeclaration(p); + } + + @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 if (instantiationTypes.containsKey(type)) { + return type; + } + } else { + type = openAPIType; + } + if (type == null) { + return null; + } + return toModelName(type); + } + + @Override + public String getTypeDeclaration(String name) { + if (!languageSpecificPrimitives.contains(name)) { + return "\\" + modelPackage + "\\" + name; + } + return super.getTypeDeclaration(name); + } + + @Override + public String toDefaultValue(Schema p) { + return "null"; + } + + public void setParameterNamingConvention(String variableNamingConvention) { + this.variableNamingConvention = variableNamingConvention; + } + + @Override + public String toVarName(String name) { + name = sanitizeName(name); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. + + if ("camelCase".equals(variableNamingConvention)) { + // return the name in camelCase style + // phone_number => phoneNumber + name = camelize(name, true); + } else { // default to snake case + // return the name in underscore style + // PhoneNumber => phone_number + name = underscore(name); + } + + // 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) { + // model name cannot use reserved keyword + if (isReservedWord(name)) { + escapeReservedWord(name); // e.g. return => _return + } + + // 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); + } + + public String toPackagePath(String packageName, String basePath) { + packageName = packageName.replace(invokerPackage, ""); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. + if (basePath != null && basePath.length() > 0) { + basePath = basePath.replaceAll("[\\\\/]?$", "") + File.separatorChar; // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. + } + + String regFirstPathSeparator; + if ("/".equals(File.separator)) { // for mac, linux + regFirstPathSeparator = "^/"; + } else { // for windows + regFirstPathSeparator = "^\\\\"; + } + + String regLastPathSeparator; + if ("/".equals(File.separator)) { // for mac, linux + regLastPathSeparator = "/$"; + } else { // for windows + regLastPathSeparator = "\\\\$"; + } + + 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+ "$", ""); + } + + public String getPackagePath() { + return packagePath; + } + + @Override + public String escapeQuotationMark(String input) { + // remove ' to avoid code injection + return input.replace("'", ""); + } + + @Override + public String escapeUnsafeCharacters(String input) { + return input.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 b9ac5373d97..c4ac26b140e 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,5 +1,16 @@ +org.openapitools.codegen.languages.AndroidClientCodegen org.openapitools.codegen.languages.BashClientCodegen +org.openapitools.codegen.languages.DartClientCodegen +org.openapitools.codegen.languages.ElixirClientCodegen org.openapitools.codegen.languages.HaskellServantCodegen +org.openapitools.codegen.languages.LumenServerCodegen org.openapitools.codegen.languages.ObjcClientCodegen org.openapitools.codegen.languages.PhpClientCodegen +org.openapitools.codegen.languages.PowerShellClientCodegen +org.openapitools.codegen.languages.PythonClientCodegen +org.openapitools.codegen.languages.RClientCodegen +org.openapitools.codegen.languages.Rails5ServerCodegen org.openapitools.codegen.languages.RubyClientCodegen +org.openapitools.codegen.languages.SlimFrameworkServerCodegen +org.openapitools.codegen.languages.SilexServerCodegen +org.openapitools.codegen.languages.SinatraServerCodegen diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/dart/DartClientOptionsTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/dart/DartClientOptionsTest.java new file mode 100644 index 00000000000..227de3285b5 --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/dart/DartClientOptionsTest.java @@ -0,0 +1,45 @@ +package org.openapitools.codegen.dart; + +import org.openapitools.codegen.AbstractOptionsTest; +import org.openapitools.codegen.CodegenConfig; +import org.openapitools.codegen.languages.DartClientCodegen; +import org.openapitools.codegen.options.DartClientOptionsProvider; + +import mockit.Expectations; +import mockit.Tested; + +public class DartClientOptionsTest extends AbstractOptionsTest { + + @Tested + private DartClientCodegen clientCodegen; + + public DartClientOptionsTest() { + super(new DartClientOptionsProvider()); + } + + @Override + protected CodegenConfig getCodegenConfig() { + return clientCodegen; + } + + @SuppressWarnings("unused") + @Override + protected void setExpectations() { + new Expectations(clientCodegen) {{ + clientCodegen.setSortParamsByRequiredFlag(Boolean.valueOf(DartClientOptionsProvider.SORT_PARAMS_VALUE)); + times = 1; + clientCodegen.setBrowserClient(Boolean.valueOf(DartClientOptionsProvider.BROWSER_CLIENT_VALUE)); + times = 1; + clientCodegen.setPubName(DartClientOptionsProvider.PUB_NAME_VALUE); + times = 1; + clientCodegen.setPubVersion(DartClientOptionsProvider.PUB_VERSION_VALUE); + times = 1; + clientCodegen.setPubDescription(DartClientOptionsProvider.PUB_DESCRIPTION_VALUE); + times = 1; + clientCodegen.setSourceFolder(DartClientOptionsProvider.SOURCE_FOLDER_VALUE); + times = 1; + clientCodegen.setUseEnumExtension(Boolean.valueOf(DartClientOptionsProvider.USE_ENUM_EXTENSION)); + times = 1; + }}; + } +} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/elixir/ElixirClientOptionsTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/elixir/ElixirClientOptionsTest.java new file mode 100644 index 00000000000..bc22e3c514f --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/elixir/ElixirClientOptionsTest.java @@ -0,0 +1,34 @@ +package org.openapitools.codegen.elixir; + +import org.openapitools.codegen.AbstractOptionsTest; +import org.openapitools.codegen.CodegenConfig; +import org.openapitools.codegen.languages.ElixirClientCodegen; +import org.openapitools.codegen.options.ElixirClientOptionsProvider; +import mockit.Expectations; +import mockit.Tested; + +public class ElixirClientOptionsTest extends AbstractOptionsTest { + + @Tested + private ElixirClientCodegen clientCodegen; + + public ElixirClientOptionsTest() { + super(new ElixirClientOptionsProvider()); + } + + @Override + protected CodegenConfig getCodegenConfig() { + return clientCodegen; + } + + @SuppressWarnings("unused") + @Override + protected void setExpectations() { + new Expectations(clientCodegen) {{ + // TODO + clientCodegen.setModuleName(ElixirClientOptionsProvider.INVOKER_PACKAGE_VALUE); + times = 1; + + }}; + } +} 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 new file mode 100644 index 00000000000..9a270837251 --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/lumen/LumenServerOptionsTest.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.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/python/PythonClientOptionsTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/python/PythonClientOptionsTest.java new file mode 100644 index 00000000000..f686ad68c10 --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/python/PythonClientOptionsTest.java @@ -0,0 +1,37 @@ +package org.openapitools.codegen.python; + +import org.openapitools.codegen.AbstractOptionsTest; +import org.openapitools.codegen.CodegenConfig; +import org.openapitools.codegen.languages.PythonClientCodegen; +import org.openapitools.codegen.options.PythonClientOptionsProvider; + +import mockit.Expectations; +import mockit.Tested; + +public class PythonClientOptionsTest extends AbstractOptionsTest { + + @Tested + private PythonClientCodegen clientCodegen; + + public PythonClientOptionsTest() { + super(new PythonClientOptionsProvider()); + } + + @Override + protected CodegenConfig getCodegenConfig() { + return clientCodegen; + } + + @SuppressWarnings("unused") + @Override + protected void setExpectations() { + new Expectations(clientCodegen) {{ + clientCodegen.setPackageName(PythonClientOptionsProvider.PACKAGE_NAME_VALUE); + clientCodegen.setProjectName(PythonClientOptionsProvider.PROJECT_NAME_VALUE); + clientCodegen.setPackageVersion(PythonClientOptionsProvider.PACKAGE_VERSION_VALUE); + clientCodegen.setPackageUrl(PythonClientOptionsProvider.PACKAGE_URL_VALUE); + // clientCodegen.setLibrary(PythonClientCodegen.DEFAULT_LIBRARY); + times = 1; + }}; + } +} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/python/PythonTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/python/PythonTest.java new file mode 100644 index 00000000000..d504e85e389 --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/python/PythonTest.java @@ -0,0 +1,275 @@ +package org.openapitools.codegen.python; + +import org.openapitools.codegen.CodegenModel; +import org.openapitools.codegen.CodegenOperation; +import org.openapitools.codegen.CodegenProperty; +import org.openapitools.codegen.DefaultCodegen; +import org.openapitools.codegen.languages.PythonClientCodegen; + +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.OpenAPIV3Parser; +import io.swagger.v3.parser.util.SchemaTypeUtil; + +import org.testng.Assert; +import org.testng.annotations.Test; +import org.testng.annotations.ITestAnnotation; + +import com.google.common.collect.Sets; +import java.util.Map; + +@SuppressWarnings("static-method") +public class PythonTest { + + @Test(description = "convert a python model with dots") + public void modelTest() { + final OpenAPI openAPI= new OpenAPIV3Parser().read("src/test/resources/2_0/v1beta3.json"); + final DefaultCodegen codegen = new PythonClientCodegen(); + + final CodegenModel simpleName = codegen.fromModel("v1beta3.Binding", openAPI.getComponents().getSchemas().get("v1beta3.Binding")); + Assert.assertEquals(simpleName.name, "v1beta3.Binding"); + Assert.assertEquals(simpleName.classname, "V1beta3Binding"); + Assert.assertEquals(simpleName.classVarName, "v1beta3_binding"); + + final CodegenModel compoundName = codegen.fromModel("v1beta3.ComponentStatus", openAPI.getComponents().getSchemas().get("v1beta3.ComponentStatus")); + Assert.assertEquals(compoundName.name, "v1beta3.ComponentStatus"); + Assert.assertEquals(compoundName.classname, "V1beta3ComponentStatus"); + Assert.assertEquals(compoundName.classVarName, "v1beta3_component_status"); + + final String path = "/api/v1beta3/namespaces/{namespaces}/bindings"; + final Operation operation = openAPI.getPaths().get(path).getPost(); + final CodegenOperation codegenOperation = codegen.fromOperation(path, "get", operation, openAPI.getComponents().getSchemas()); + Assert.assertEquals(codegenOperation.returnType, "V1beta3Binding"); + Assert.assertEquals(codegenOperation.returnBaseType, "V1beta3Binding"); + } + + @Test(description = "convert a simple java model") + public void simpleModelTest() { + final Schema schema = 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 PythonClientCodegen(); + final CodegenModel cm = codegen.fromModel("sample", schema); + + Assert.assertEquals(cm.name, "sample"); + Assert.assertEquals(cm.classname, "Sample"); + Assert.assertEquals(cm.description, "a sample model"); + Assert.assertEquals(cm.vars.size(), 3); + + final CodegenProperty property1 = cm.vars.get(0); + Assert.assertEquals(property1.baseName, "id"); + Assert.assertEquals(property1.datatype, "int"); + Assert.assertEquals(property1.name, "id"); + Assert.assertNull(property1.defaultValue); + Assert.assertEquals(property1.baseType, "int"); + 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, "str"); + Assert.assertEquals(property2.name, "name"); + Assert.assertNull(property2.defaultValue); + Assert.assertEquals(property2.baseType, "str"); + 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.datatype, "datetime"); + Assert.assertEquals(property3.name, "created_at"); + Assert.assertNull(property3.defaultValue); + Assert.assertEquals(property3.baseType, "datetime"); + 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 PythonClientCodegen(); + 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, "int"); + Assert.assertEquals(property1.name, "id"); + Assert.assertNull(property1.defaultValue); + Assert.assertEquals(property1.baseType, "int"); + 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, "list[str]"); + Assert.assertEquals(property2.name, "urls"); + Assert.assertNull(property2.defaultValue); + Assert.assertEquals(property2.baseType, "list"); + 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 PythonClientCodegen(); + 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, "dict(str, str)"); + Assert.assertEquals(property1.name, "translations"); + Assert.assertEquals(property1.baseType, "dict"); + 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 PythonClientCodegen(); + 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 complexListPropertyTest() { + final Schema model = new Schema() + .description("a sample model") + .addProperties("children", new ArraySchema() + .items(new Schema().$ref("#/definitions/Children"))); + final DefaultCodegen codegen = new PythonClientCodegen(); + 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.complexType, "Children"); + Assert.assertEquals(property1.datatype, "list[Children]"); + Assert.assertEquals(property1.name, "children"); + Assert.assertEquals(property1.baseType, "list"); + Assert.assertEquals(property1.containerType, "array"); + Assert.assertFalse(property1.required); + Assert.assertTrue(property1.isContainer); + } + + @Test(description = "convert a model with complex map property") + public void complexMapPropertyTest() { + final Schema model = new Schema() + .description("a sample model") + .addProperties("children", new MapSchema() + .additionalProperties(new Schema().$ref("#/definitions/Children"))); + final DefaultCodegen codegen = new PythonClientCodegen(); + 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, "dict(str, Children)"); + Assert.assertEquals(property1.name, "children"); + Assert.assertEquals(property1.baseType, "dict"); + Assert.assertEquals(property1.containerType, "map"); + Assert.assertFalse(property1.required); + Assert.assertTrue(property1.isContainer); + Assert.assertFalse(property1.isNotContainer); + } + + + // should not start with 'null'. need help from the community to investigate further + @Test(enabled = false, description = "convert an array model") + public void arrayModelTest() { + final Schema model = new ArraySchema() + //.description() + .items(new Schema().$ref("#/definitions/Children")) + .description("an array model"); + final DefaultCodegen codegen = new PythonClientCodegen(); + 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.parent, "null"); + Assert.assertEquals(cm.imports.size(), 1); + Assert.assertEquals(Sets.intersection(cm.imports, Sets.newHashSet("Children")).size(), 1); + } + + // should not start with 'null'. need help from the community to investigate further + @Test(enabled = false, description = "convert an map model") + public void mapModelTest() { + final Schema model = new Schema() + .description("a map model") + .additionalProperties(new Schema().$ref("#/definitions/Children")); + final DefaultCodegen codegen = new PythonClientCodegen(); + 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.parent, "null"); + Assert.assertEquals(cm.imports.size(), 2); // TODO: need to verify + Assert.assertEquals(Sets.intersection(cm.imports, Sets.newHashSet("Children")).size(), 1); + } + +} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/ruby/RubyClientCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/ruby/RubyClientCodegenTest.java new file mode 100644 index 00000000000..fc923bde1d7 --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/ruby/RubyClientCodegenTest.java @@ -0,0 +1,70 @@ +package org.openapitools.codegen.ruby; + +import org.openapitools.codegen.ClientOpts; +import org.openapitools.codegen.ClientOptInput; +import org.openapitools.codegen.CodegenConfig; +import org.openapitools.codegen.DefaultGenerator; +import org.openapitools.codegen.languages.RubyClientCodegen; + +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.OpenAPIV3Parser; +import io.swagger.v3.parser.util.SchemaTypeUtil; + +import org.apache.commons.io.FileUtils; +import org.junit.rules.TemporaryFolder; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import static org.junit.Assert.fail; +import static org.testng.Assert.*; + +/** + * Tests for RubyClientCodegen-generated templates + */ +public class RubyClientCodegenTest { + + public TemporaryFolder folder = new TemporaryFolder(); + + @BeforeMethod + public void setUp() throws Exception { + folder.create(); + } + + @AfterMethod + public void tearDown() throws Exception { + folder.delete(); + } + + @Test + public void testGenerateRubyClientWithHtmlEntity() throws Exception { + final File output = folder.getRoot(); + + final OpenAPI openAPI = new OpenAPIV3Parser().read("src/test/resources/2_0/pathWithHtmlEntity.yaml"); + CodegenConfig codegenConfig = new RubyClientCodegen(); + codegenConfig.setOutputDir(output.getAbsolutePath()); + + ClientOptInput clientOptInput = new ClientOptInput().opts(new ClientOpts()).openAPI(openAPI).config(codegenConfig); + + DefaultGenerator generator = new DefaultGenerator(); + List files = generator.opts(clientOptInput).generate(); + boolean apiFileGenerated = false; + for (File file : files) { + if (file.getName().equals("default_api.rb")) { + apiFileGenerated = true; + // Ruby client should set the path unescaped in the api file + assertTrue(FileUtils.readFileToString(file, StandardCharsets.UTF_8).contains("local_var_path = '/foo=bar'")); + } + } + if (!apiFileGenerated) { + fail("Default api file is not generated!"); + } + } + +} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/ruby/RubyClientOptionsTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/ruby/RubyClientOptionsTest.java new file mode 100644 index 00000000000..21cc3672205 --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/ruby/RubyClientOptionsTest.java @@ -0,0 +1,52 @@ +package org.openapitools.codegen.ruby; + +import org.openapitools.codegen.AbstractOptionsTest; +import org.openapitools.codegen.CodegenConfig; +import org.openapitools.codegen.languages.RubyClientCodegen; +import org.openapitools.codegen.options.RubyClientOptionsProvider; + +import mockit.Expectations; +import mockit.Tested; + +public class RubyClientOptionsTest extends AbstractOptionsTest { + + @Tested + private RubyClientCodegen clientCodegen; + + public RubyClientOptionsTest() { + super(new RubyClientOptionsProvider()); + } + + @Override + protected CodegenConfig getCodegenConfig() { + return clientCodegen; + } + + @SuppressWarnings("unused") + @Override + protected void setExpectations() { + new Expectations(clientCodegen) {{ + clientCodegen.setGemName(RubyClientOptionsProvider.GEM_NAME_VALUE); + times = 1; + clientCodegen.setModuleName(RubyClientOptionsProvider.MODULE_NAME_VALUE); + times = 1; + clientCodegen.setGemVersion(RubyClientOptionsProvider.GEM_VERSION_VALUE); + times = 1; + clientCodegen.setGemLicense(RubyClientOptionsProvider.GEM_LICENSE_VALUE); + times = 1; + clientCodegen.setGemRequiredRubyVersion(RubyClientOptionsProvider.GEM_REQUIRED_RUBY_VERSION_VALUE); + times = 1; + clientCodegen.setGemHomepage(RubyClientOptionsProvider.GEM_HOMEPAGE_VALUE); + times = 1; + clientCodegen.setGemDescription(RubyClientOptionsProvider.GEM_DESCRIPTION_VALUE); + times = 1; + clientCodegen.setGemSummary(RubyClientOptionsProvider.GEM_SUMMARY_VALUE); + times = 1; + clientCodegen.setGemAuthor(RubyClientOptionsProvider.GEM_AUTHOR_VALUE); + times = 1; + clientCodegen.setGemAuthorEmail(RubyClientOptionsProvider.GEM_AUTHOR_EMAIL_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/SilexServerOptionsTest.java new file mode 100644 index 00000000000..3f8ac24de59 --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/silex/SilexServerOptionsTest.java @@ -0,0 +1,33 @@ +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 mockit.Expectations; +import mockit.Tested; + +public class SilexServerOptionsTest extends AbstractOptionsTest { + + @Tested + private SilexServerCodegen clientCodegen; + + public SilexServerOptionsTest() { + super(new SilexServerOptionsProvider()); + } + + @Override + protected CodegenConfig getCodegenConfig() { + return clientCodegen; + } + + @SuppressWarnings("unused") + @Override + protected void setExpectations() { + new Expectations(clientCodegen) {{ + clientCodegen.setSortParamsByRequiredFlag(Boolean.valueOf(SilexServerOptionsProvider.SORT_PARAMS_VALUE)); + times = 1; + }}; + } +} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/sinatra/SinatraServerOptionsTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/sinatra/SinatraServerOptionsTest.java new file mode 100644 index 00000000000..3f906bd8c3f --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/sinatra/SinatraServerOptionsTest.java @@ -0,0 +1,31 @@ +package org.openapitools.codegen.sinatra; + +import org.openapitools.codegen.AbstractOptionsTest; +import org.openapitools.codegen.CodegenConfig; +import org.openapitools.codegen.languages.SinatraServerCodegen; +import org.openapitools.codegen.options.SinatraServerOptionsProvider; + +import mockit.Expectations; +import mockit.Tested; + +public class SinatraServerOptionsTest extends AbstractOptionsTest { + + @Tested + private SinatraServerCodegen clientCodegen; + + public SinatraServerOptionsTest() { + super(new SinatraServerOptionsProvider()); + } + + @Override + protected CodegenConfig getCodegenConfig() { + return clientCodegen; + } + + @SuppressWarnings("unused") + @Override + protected void setExpectations() { + new Expectations(clientCodegen) {{ + }}; + } +} 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/SlimFrameworkServerOptionsTest.java new file mode 100644 index 00000000000..6e2e06e3a67 --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/slim/SlimFrameworkServerOptionsTest.java @@ -0,0 +1,33 @@ +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 mockit.Expectations; +import mockit.Tested; + +public class SlimFrameworkServerOptionsTest extends AbstractOptionsTest { + + @Tested + private SlimFrameworkServerCodegen clientCodegen; + + public SlimFrameworkServerOptionsTest() { + super(new SlimFrameworkServerOptionsProvider()); + } + + @Override + protected CodegenConfig getCodegenConfig() { + return clientCodegen; + } + + @SuppressWarnings("unused") + @Override + protected void setExpectations() { + new Expectations(clientCodegen) {{ + clientCodegen.setSortParamsByRequiredFlag(Boolean.valueOf(SlimFrameworkServerOptionsProvider.SORT_PARAMS_VALUE)); + times = 1; + }}; + } +} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/options/DartClientOptionsProvider.java b/modules/openapi-generator/src/test/java/org/openapitools/options/DartClientOptionsProvider.java new file mode 100644 index 00000000000..e0ead704054 --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/options/DartClientOptionsProvider.java @@ -0,0 +1,47 @@ +package org.openapitools.codegen.options; + +import org.openapitools.codegen.CodegenConstants; +import org.openapitools.codegen.languages.DartClientCodegen; + +import com.google.common.collect.ImmutableMap; + +import java.util.Map; + +public class DartClientOptionsProvider implements OptionsProvider { + public static final String SORT_PARAMS_VALUE = "true"; + public static final String ENSURE_UNIQUE_PARAMS_VALUE = "true"; + public static final String BROWSER_CLIENT_VALUE = "true"; + public static final String PUB_NAME_VALUE = "swagger"; + public static final String PUB_VERSION_VALUE = "1.0.0-SNAPSHOT"; + public static final String PUB_DESCRIPTION_VALUE = "Swagger API client dart"; + public static final String SOURCE_FOLDER_VALUE = "src"; + public static final String USE_ENUM_EXTENSION = "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 "dart"; + } + + @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(DartClientCodegen.BROWSER_CLIENT, BROWSER_CLIENT_VALUE) + .put(DartClientCodegen.PUB_NAME, PUB_NAME_VALUE) + .put(DartClientCodegen.PUB_VERSION, PUB_VERSION_VALUE) + .put(DartClientCodegen.PUB_DESCRIPTION, PUB_DESCRIPTION_VALUE) + .put(CodegenConstants.SOURCE_FOLDER, SOURCE_FOLDER_VALUE) + .put(DartClientCodegen.USE_ENUM_EXTENSION, USE_ENUM_EXTENSION) + .put(CodegenConstants.ALLOW_UNICODE_IDENTIFIERS, ALLOW_UNICODE_IDENTIFIERS_VALUE) + .put(CodegenConstants.PREPEND_FORM_OR_BODY_PARAMETERS, PREPEND_FORM_OR_BODY_PARAMETERS_VALUE) + .build(); + } + + @Override + public boolean isServer() { + return false; + } +} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/options/ElixirClientOptionsProvider.java b/modules/openapi-generator/src/test/java/org/openapitools/options/ElixirClientOptionsProvider.java new file mode 100644 index 00000000000..5669068e750 --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/options/ElixirClientOptionsProvider.java @@ -0,0 +1,35 @@ +package org.openapitools.codegen.options; + +import com.google.common.collect.ImmutableMap; +import org.openapitools.codegen.CodegenConstants; + +import java.util.Map; + +public class ElixirClientOptionsProvider implements OptionsProvider { + public static final String INVOKER_PACKAGE_VALUE = "Yay.Pets"; + public static final String PREPEND_FORM_OR_BODY_PARAMETERS_VALUE = "true"; + + @Override + public String getLanguage() { + return "elixir"; + } + + @Override + public Map createOptions() { + ImmutableMap.Builder builder = new ImmutableMap.Builder(); + return builder + .put(CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG, "false") + .put(CodegenConstants.ENSURE_UNIQUE_PARAMS, "false") + .put(CodegenConstants.ALLOW_UNICODE_IDENTIFIERS, "false") + .put(CodegenConstants.INVOKER_PACKAGE, "Yay.Pets") + .put("licenseHeader", "# Copyright 2017 Me\n#\n# Licensed under the Apache License") + .put(CodegenConstants.PACKAGE_NAME, "yay_pets") + .put(CodegenConstants.PREPEND_FORM_OR_BODY_PARAMETERS, PREPEND_FORM_OR_BODY_PARAMETERS_VALUE) + .build(); + } + + @Override + public boolean isServer() { + return false; + } +} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/options/LumenServerOptionsProvider.java b/modules/openapi-generator/src/test/java/org/openapitools/options/LumenServerOptionsProvider.java new file mode 100644 index 00000000000..ad7e7d8fef5 --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/options/LumenServerOptionsProvider.java @@ -0,0 +1,53 @@ +package org.openapitools.codegen.options; + +import org.openapitools.codegen.CodegenConstants; +import org.openapitools.codegen.languages.AbstractPhpCodegen; + +import com.google.common.collect.ImmutableMap; + +import java.util.Map; + +public class LumenServerOptionsProvider 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"; + public static final String ENSURE_UNIQUE_PARAMS_VALUE = "true"; + public static final String VARIABLE_NAMING_CONVENTION_VALUE = "snake_case"; + public static final String INVOKER_PACKAGE_VALUE = "lumen"; + public static final String PACKAGE_PATH_VALUE = "php"; + public static final String SRC_BASE_PATH_VALUE = "libPhp"; + public static final String GIT_USER_ID_VALUE = "gitSwaggerPhp"; + public static final String GIT_REPO_ID_VALUE = "git-swagger-php"; + public static final String ARTIFACT_VERSION_VALUE = "1.0.0-SNAPSHOT"; + 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 "php-lumen"; + } + + @Override + public Map createOptions() { + ImmutableMap.Builder builder = new ImmutableMap.Builder(); + return builder.put(CodegenConstants.MODEL_PACKAGE, MODEL_PACKAGE_VALUE) + .put(AbstractPhpCodegen.VARIABLE_NAMING_CONVENTION, VARIABLE_NAMING_CONVENTION_VALUE) + .put(AbstractPhpCodegen.PACKAGE_PATH, PACKAGE_PATH_VALUE) + .put(AbstractPhpCodegen.SRC_BASE_PATH, SRC_BASE_PATH_VALUE) + .put(CodegenConstants.API_PACKAGE, API_PACKAGE_VALUE) + .put(CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG, SORT_PARAMS_VALUE) + .put(CodegenConstants.ENSURE_UNIQUE_PARAMS, ENSURE_UNIQUE_PARAMS_VALUE) + .put(CodegenConstants.INVOKER_PACKAGE, INVOKER_PACKAGE_VALUE) + .put(CodegenConstants.GIT_USER_ID, GIT_USER_ID_VALUE) + .put(CodegenConstants.GIT_REPO_ID, GIT_REPO_ID_VALUE) + .put(CodegenConstants.ARTIFACT_VERSION, ARTIFACT_VERSION_VALUE) + .put(CodegenConstants.ALLOW_UNICODE_IDENTIFIERS, ALLOW_UNICODE_IDENTIFIERS_VALUE) + .put(CodegenConstants.PREPEND_FORM_OR_BODY_PARAMETERS, PREPEND_FORM_OR_BODY_PARAMETERS_VALUE) + .build(); + } + + @Override + public boolean isServer() { + return true; + } +} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/options/PythonClientOptionsProvider.java b/modules/openapi-generator/src/test/java/org/openapitools/options/PythonClientOptionsProvider.java new file mode 100644 index 00000000000..0e55549e376 --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/options/PythonClientOptionsProvider.java @@ -0,0 +1,38 @@ +package org.openapitools.codegen.options; + +import org.openapitools.codegen.CodegenConstants; +import org.openapitools.codegen.languages.PythonClientCodegen; + +import com.google.common.collect.ImmutableMap; + +import java.util.Map; + +public class PythonClientOptionsProvider implements OptionsProvider { + public static final String PACKAGE_NAME_VALUE = "swagger_client_python"; + public static final String PROJECT_NAME_VALUE = "swagger-client-python"; + public static final String PACKAGE_VERSION_VALUE = "1.0.0-SNAPSHOT"; + public static final String PACKAGE_URL_VALUE = ""; + + @Override + public String getLanguage() { + return "python"; + } + + @Override + public Map createOptions() { + ImmutableMap.Builder builder = new ImmutableMap.Builder(); + return builder.put(PythonClientCodegen.PACKAGE_URL, PACKAGE_URL_VALUE) + .put(CodegenConstants.PACKAGE_NAME, PACKAGE_NAME_VALUE) + .put(CodegenConstants.PROJECT_NAME, PROJECT_NAME_VALUE) + .put(CodegenConstants.PACKAGE_VERSION, PACKAGE_VERSION_VALUE) + .put(CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG, "true") + .put(CodegenConstants.HIDE_GENERATION_TIMESTAMP, "true") + .put(CodegenConstants.LIBRARY, "urllib3") + .build(); + } + + @Override + public boolean isServer() { + return false; + } +} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/options/RubyClientOptionsProvider.java b/modules/openapi-generator/src/test/java/org/openapitools/options/RubyClientOptionsProvider.java new file mode 100644 index 00000000000..51c6ae49cc6 --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/options/RubyClientOptionsProvider.java @@ -0,0 +1,56 @@ +package org.openapitools.codegen.options; + +import org.openapitools.codegen.CodegenConstants; +import org.openapitools.codegen.languages.RubyClientCodegen; + +import com.google.common.collect.ImmutableMap; + +import java.util.Map; + +public class RubyClientOptionsProvider implements OptionsProvider { + public static final String GEM_NAME_VALUE = "swagger_client_ruby"; + public static final String MODULE_NAME_VALUE = "SwaggerClientRuby"; + public static final String GEM_VERSION_VALUE = "1.0.0-SNAPSHOT"; + public static final String SORT_PARAMS_VALUE = "false"; + public static final String ENSURE_UNIQUE_PARAMS_VALUE = "true"; + public static final String GEM_LICENSE_VALUE = "MIT"; + public static final String GEM_REQUIRED_RUBY_VERSION_VALUE = ">= 1.9"; + public static final String GEM_HOMEPAGE_VALUE = "homepage"; + public static final String GEM_SUMMARY_VALUE = "summary"; + public static final String GEM_DESCRIPTION_VALUE = "description"; + public static final String GEM_AUTHOR_VALUE = "foo"; + public static final String GEM_AUTHOR_EMAIL_VALUE = "foo"; + 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 "ruby"; + } + + @Override + public Map createOptions() { + ImmutableMap.Builder builder = new ImmutableMap.Builder(); + return builder.put(RubyClientCodegen.GEM_NAME, GEM_NAME_VALUE) + .put(RubyClientCodegen.MODULE_NAME, MODULE_NAME_VALUE) + .put(RubyClientCodegen.GEM_VERSION, GEM_VERSION_VALUE) + .put(RubyClientCodegen.GEM_LICENSE, GEM_LICENSE_VALUE) + .put(RubyClientCodegen.GEM_REQUIRED_RUBY_VERSION, GEM_REQUIRED_RUBY_VERSION_VALUE) + .put(RubyClientCodegen.GEM_DESCRIPTION, GEM_DESCRIPTION_VALUE) + .put(RubyClientCodegen.GEM_HOMEPAGE, GEM_HOMEPAGE_VALUE) + .put(RubyClientCodegen.GEM_SUMMARY, GEM_SUMMARY_VALUE) + .put(RubyClientCodegen.GEM_AUTHOR, GEM_AUTHOR_VALUE) + .put(RubyClientCodegen.GEM_AUTHOR_EMAIL, GEM_AUTHOR_EMAIL_VALUE) + .put(CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG, SORT_PARAMS_VALUE) + .put(CodegenConstants.ENSURE_UNIQUE_PARAMS, ENSURE_UNIQUE_PARAMS_VALUE) + .put(CodegenConstants.HIDE_GENERATION_TIMESTAMP, "true") + .put(CodegenConstants.ALLOW_UNICODE_IDENTIFIERS, ALLOW_UNICODE_IDENTIFIERS_VALUE) + .put(CodegenConstants.PREPEND_FORM_OR_BODY_PARAMETERS, PREPEND_FORM_OR_BODY_PARAMETERS_VALUE) + .build(); + } + + @Override + public boolean isServer() { + return 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/SilexServerOptionsProvider.java new file mode 100644 index 00000000000..de4a9202611 --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/options/SilexServerOptionsProvider.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 SilexServerOptionsProvider 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 "php-silex"; + } + + @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, PREPEND_FORM_OR_BODY_PARAMETERS_VALUE) + .build(); + } + + @Override + public boolean isServer() { + return true; + } +} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/options/SinatraServerOptionsProvider.java b/modules/openapi-generator/src/test/java/org/openapitools/options/SinatraServerOptionsProvider.java new file mode 100644 index 00000000000..51f1c2cef1d --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/options/SinatraServerOptionsProvider.java @@ -0,0 +1,23 @@ +package org.openapitools.codegen.options; + +import com.google.common.collect.ImmutableMap; + +import java.util.Map; + +public class SinatraServerOptionsProvider implements OptionsProvider { + @Override + public String getLanguage() { + return "ruby-sinatra"; + } + + @Override + public Map createOptions() { + //SinatraServerCodegen doesn't have its own options and base options are cleared + return ImmutableMap.of(); + } + + @Override + public boolean isServer() { + return true; + } +} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/options/SlimFrameworkServerOptionsProvider.java b/modules/openapi-generator/src/test/java/org/openapitools/options/SlimFrameworkServerOptionsProvider.java new file mode 100644 index 00000000000..22f3a231f4b --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/options/SlimFrameworkServerOptionsProvider.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 SlimFrameworkServerOptionsProvider 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 "slim"; + } + + @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, PREPEND_FORM_OR_BODY_PARAMETERS_VALUE) + .build(); + } + + @Override + public boolean isServer() { + return true; + } +} diff --git a/modules/openapi-generator/src/test/resources/2_0/pathWithHtmlEntity.yaml b/modules/openapi-generator/src/test/resources/2_0/pathWithHtmlEntity.yaml index 929a5cd8f9b..cf5fe1d5100 100644 --- a/modules/openapi-generator/src/test/resources/2_0/pathWithHtmlEntity.yaml +++ b/modules/openapi-generator/src/test/resources/2_0/pathWithHtmlEntity.yaml @@ -1,10 +1,18 @@ ---- +--- swagger: "2.0" -basePath: "/" -paths: - /foo=bar: - get: - parameters: [] - responses: - 200: - description: "success" +basePath: / +info: + description: "Test for response code default" + title: "path with html entity test" + version: "1.0.0" +paths: + /foo=bar: + get: + produces: + - application/json + responses: + 200: + description: "successful operation" + default: + description: "Internal server error" + summary: Test