From d2ebefd4d54f43adf38ddd30dbf306c2189f2a87 Mon Sep 17 00:00:00 2001 From: wing328 Date: Tue, 27 Mar 2018 15:15:08 +0800 Subject: [PATCH] add php lumen generator --- .../codegen/languages/AbstractPhpCodegen.java | 664 ++++++++++++++++++ .../codegen/languages/LumenServerCodegen.java | 128 ++++ .../org.openapitools.codegen.CodegenConfig | 1 + .../codegen/lumen/LumenServerOptionsTest.java | 51 ++ .../options/LumenServerOptionsProvider.java | 53 ++ 5 files changed, 897 insertions(+) create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractPhpCodegen.java create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/LumenServerCodegen.java create mode 100644 modules/openapi-generator/src/test/java/org/openapitools/codegen/lumen/LumenServerOptionsTest.java create mode 100644 modules/openapi-generator/src/test/java/org/openapitools/options/LumenServerOptionsProvider.java 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 000000000000..c9ac464189bc --- /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/LumenServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/LumenServerCodegen.java new file mode 100644 index 000000000000..78513fd0bca6 --- /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/resources/META-INF/services/org.openapitools.codegen.CodegenConfig b/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig index 15f20f51d8a1..63c901e2d6b6 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,6 @@ org.openapitools.codegen.languages.BashClientCodegen org.openapitools.codegen.languages.HaskellServantCodegen +org.openapitools.codegen.languages.LumenServerCodegen org.openapitools.codegen.languages.ObjcClientCodegen org.openapitools.codegen.languages.PhpClientCodegen org.openapitools.codegen.languages.PythonClientCodegen 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 000000000000..9a2708372511 --- /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/options/LumenServerOptionsProvider.java b/modules/openapi-generator/src/test/java/org/openapitools/options/LumenServerOptionsProvider.java new file mode 100644 index 000000000000..90c5b90a1c6e --- /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 "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; + } +}