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/resources/META-INF/services/org.openapitools.codegen.CodegenConfig b/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig index 4dfb565b97f..7237e2bd774 100644 --- a/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig +++ b/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig @@ -5,6 +5,7 @@ org.openapitools.codegen.languages.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