diff --git a/docs/debugging.md b/docs/debugging.md index 783a6012bec..f35290ee3c3 100644 --- a/docs/debugging.md +++ b/docs/debugging.md @@ -3,6 +3,76 @@ id: debugging title: Debugging --- +## Generation + +As a user there may be times when generated outputs don't match your expectations it's unclear why. The CLI supports a `--dry-run` option which may be used to inspect the anticipated file operations without making changes to the file system. + +Suppose you generate using the `--minimal-update` option, and you notice on subsequent generations of a client that no files have changed. This is by design. + +For example, if you generate the aspnetcore generator passing `--minimal-update --dry-run` to the sample generation script in the code repository: + +```bash +export JAVA_OPTS="-Dlog.level=off" +./bin/aspnetcore-petstore-server.sh --minimal-update --dry-run +``` + +You'll see the output similar to the following: + +``` +# START SCRIPT: ./bin/aspnetcore-petstore-server.sh + + +Dry Run Results: + +s /path/to/aspnetcore/.openapi-generator-ignore +n /path/to/aspnetcore/.openapi-generator/VERSION +n /path/to/aspnetcore/Org.OpenAPITools.sln +n /path/to/aspnetcore/README.md +n /path/to/aspnetcore/build.bat +n /path/to/aspnetcore/build.sh +w /path/to/aspnetcore/src/Org.OpenAPITools/.gitignore +n /path/to/aspnetcore/src/Org.OpenAPITools/Attributes/ValidateModelStateAttribute.cs +n /path/to/aspnetcore/src/Org.OpenAPITools/Authentication/ApiAuthentication.cs +n /path/to/aspnetcore/src/Org.OpenAPITools/Controllers/PetApi.cs +n /path/to/aspnetcore/src/Org.OpenAPITools/Controllers/StoreApi.cs +n /path/to/aspnetcore/src/Org.OpenAPITools/Controllers/UserApi.cs +n /path/to/aspnetcore/src/Org.OpenAPITools/Converters/CustomEnumConverter.cs +n /path/to/aspnetcore/src/Org.OpenAPITools/Dockerfile +n /path/to/aspnetcore/src/Org.OpenAPITools/Filters/BasePathFilter.cs +n /path/to/aspnetcore/src/Org.OpenAPITools/Filters/GeneratePathParamsValidationFilter.cs +n /path/to/aspnetcore/src/Org.OpenAPITools/Models/ApiResponse.cs +n /path/to/aspnetcore/src/Org.OpenAPITools/Models/Category.cs +n /path/to/aspnetcore/src/Org.OpenAPITools/Models/Order.cs +n /path/to/aspnetcore/src/Org.OpenAPITools/Models/Pet.cs +n /path/to/aspnetcore/src/Org.OpenAPITools/Models/Tag.cs +n /path/to/aspnetcore/src/Org.OpenAPITools/Models/User.cs +n /path/to/aspnetcore/src/Org.OpenAPITools/Org.OpenAPITools.csproj +n /path/to/aspnetcore/src/Org.OpenAPITools/Program.cs +w /path/to/aspnetcore/src/Org.OpenAPITools/Properties/launchSettings.json +n /path/to/aspnetcore/src/Org.OpenAPITools/Startup.cs +w /path/to/aspnetcore/src/Org.OpenAPITools/appsettings.json +w /path/to/aspnetcore/src/Org.OpenAPITools/wwwroot/README.md +w /path/to/aspnetcore/src/Org.OpenAPITools/wwwroot/index.html +n /path/to/aspnetcore/src/Org.OpenAPITools/wwwroot/openapi-original.json +w /path/to/aspnetcore/src/Org.OpenAPITools/wwwroot/web.config + + +States: + + - w Write + - n Write if New/Updated + - i Ignored + - s Skipped Overwrite + - k Skipped by user option(s) + - e Error evaluating file write state + +``` + +The output lists the files which would be written in a normal run of the tool. Notice that we skip `.openapi-generator-ignore` because the file exists and we don't want to blow away the user's generation rules. Most of these files will overwrite output files only if the contents slated for write are different from those on the filesystem; this is denoted by an `n` preceding the filename. Some of the above lines begin with a `w`, meaning these files will _always_ result in a write operation. + +If you find an operation that you feel should result in a different state, please [open an issue](https://github.com/OpenAPITools/openapi-generator/issues/new/choose) or [submit a pull request](https://github.com/OpenAPITools/openapi-generator/compare) to change the behavior (we welcome all contributions). + + ## Templates Sometimes, you may have issues with variables in your templates. As discussed in the [templating](./templating.md) docs, we offer a variety of system properties for inspecting the models bound to templates. diff --git a/docs/usage.md b/docs/usage.md index c142b55559d..8b842d55661 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -242,16 +242,16 @@ NAME SYNOPSIS openapi-generator-cli generate [(-a | --auth )] - [--api-package ] [--artifact-id ] - [--artifact-version ] + [--api-name-suffix ] [--api-package ] + [--artifact-id ] [--artifact-version ] [(-c | --config )] - [-D ...] + [-D ...] [--dry-run] [(-e | --engine )] [--enable-post-process-file] [(-g | --generator-name )] - [--generate-alias-as-model] [--git-repo-id ] - [--git-user-id ] [--group-id ] - [--http-user-agent ] + [--generate-alias-as-model] [--git-host ] + [--git-repo-id ] [--git-user-id ] + [--group-id ] [--http-user-agent ] (-i | --input-spec ) [--ignore-file-override ] [--import-mappings ...] @@ -283,6 +283,11 @@ OPTIONS remotely. Pass in a URL-encoded string of name:header with a comma separating multiple values + --api-name-suffix + Suffix that will be appended to all API names ('tags'). Default: + Api. e.g. Pet => PetApi. Note: Only ruby, python, jaxrs generators + suppport this feature at the moment. + --api-package package for generated api classes @@ -295,23 +300,26 @@ OPTIONS generated library's filename -c , --config - Path to configuration file configuration file. It can be json or - yaml.If file is json, the content should have the format - {"optionKey":"optionValue", "optionKey1":"optionValue1"...}.If file - is yaml, the content should have the format optionKey: - optionValueSupported options can be different for each language. Run - config-help -g {generator name} command for language specific config - options. + Path to configuration file. It can be JSON or YAML. If file is JSON, + the content should have the format {"optionKey":"optionValue", + "optionKey1":"optionValue1"...}. If file is YAML, the content should + have the format optionKey: optionValue. Supported options can be + different for each language. Run config-help -g {generator name} + command for language-specific config options. -D sets specified system properties in the format of name=value,name=value (or multiple options, each with name=value) + --dry-run + Try things out and report on potential changes (without actually + making changes). + -e , --engine templating engine: "mustache" (default) or "handlebars" (beta) --enable-post-process-file - enablePostProcessFile + Enable post-processing file using environment variables. -g , --generator-name generator to use (see list command for list) @@ -324,6 +332,9 @@ OPTIONS i.e. the 'additionalproperties' attribute is set on that object. An 'array' schema is a list of sub schemas in a OAS document. + --git-host + Git host, e.g. gitlab.com. + --git-repo-id Git repo ID, e.g. openapi-generator. @@ -377,12 +388,10 @@ OPTIONS Only write output files that have changed. --model-name-prefix - Prefix that will be prepended to all model names. Default is the - empty string. + Prefix that will be prepended to all model names. --model-name-suffix - Suffix that will be appended to all model names. Default is the - empty string. + Suffix that will be appended to all model names. --model-package package for generated models @@ -415,8 +424,8 @@ OPTIONS generation. --server-variables - sets server variables for spec documents which support variable - templating of servers. + sets server variables overrides for spec documents which support + variable templating of servers. --skip-validate-spec Skips the default behavior of validating an input specification. diff --git a/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/Generate.java b/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/Generate.java index df19b0bb5fe..976aa7892fb 100644 --- a/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/Generate.java +++ b/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/Generate.java @@ -91,6 +91,9 @@ public class Generate implements Runnable { + "overwritten during the generation.") private Boolean skipOverwrite; + @Option(name = { "--dry-run" }, title = "Dry run", + description = "Try things out and report on potential changes (without actually making changes).") + private Boolean isDryRun; @Option(name = {"--package-name"}, title = "package name", description = CodegenConstants.PACKAGE_NAME_DESC) @@ -416,7 +419,7 @@ public class Generate implements Runnable { // this null check allows us to inject for unit testing. if (generator == null) { - generator = new DefaultGenerator(); + generator = new DefaultGenerator(isDryRun); } generator.opts(clientOptInput); diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/AbstractGenerator.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/AbstractGenerator.java index ba17f231aeb..3a2fecbc541 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/AbstractGenerator.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/AbstractGenerator.java @@ -17,7 +17,7 @@ package org.openapitools.codegen; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.StandardCopyOption; import java.util.Arrays; @@ -28,12 +28,16 @@ import org.slf4j.LoggerFactory; import java.io.*; import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; import java.util.Scanner; import java.util.regex.Pattern; public abstract class AbstractGenerator implements TemplatingGenerator { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractGenerator.class); - + protected boolean dryRun = false; + protected Map dryRunStatusMap = new HashMap<>(); + /** * Is the minimal-file-update option enabled? * @@ -50,19 +54,19 @@ public abstract class AbstractGenerator implements TemplatingGenerator { * @throws IOException If file cannot be written. */ public File writeToFile(String filename, String contents) throws IOException { - return writeToFile(filename, contents.getBytes(Charset.forName("UTF-8"))); + return writeToFile(filename, contents.getBytes(StandardCharsets.UTF_8)); } /** * Write bytes to a file * * @param filename The name of file to write - * @param contents The contents bytes. Typically this is a UTF-8 formatted string. + * @param contents The contents bytes. Typically, this is a UTF-8 formatted string. * @return File representing the written file. * @throws IOException If file cannot be written. */ @SuppressWarnings("static-method") - public File writeToFile(String filename, byte contents[]) throws IOException { + public File writeToFile(String filename, byte[] contents) throws IOException { if (getEnableMinimalUpdate()) { String tempFilename = filename + ".tmp"; // Use Paths.get here to normalize path (for Windows file separator, space escaping on Linux/Mac, etc) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java index d2f9a078b50..4a8cea8b00b 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java @@ -53,6 +53,9 @@ import java.nio.file.Path; import java.time.ZonedDateTime; import java.util.*; +import static org.openapitools.codegen.utils.OnceLogger.once; + +@SuppressWarnings("rawtypes") public class DefaultGenerator extends AbstractGenerator implements Generator { protected final Logger LOGGER = LoggerFactory.getLogger(DefaultGenerator.class); protected CodegenConfig config; @@ -73,11 +76,21 @@ public class DefaultGenerator extends AbstractGenerator implements Generator { private String contextPath; private Map generatorPropertyDefaults = new HashMap<>(); + + public DefaultGenerator() { + } + + public DefaultGenerator(Boolean dryRun) { + this.dryRun = Boolean.TRUE.equals(dryRun); + LOGGER.info("Generating with dryRun={}", this.dryRun); + } + @Override public boolean getEnableMinimalUpdate() { return config.isEnableMinimalUpdate(); } + @SuppressWarnings("deprecation") @Override public Generator opts(ClientOptInput opts) { this.opts = opts; @@ -285,17 +298,35 @@ public class DefaultGenerator extends AbstractGenerator implements Generator { for (String templateName : config.modelTestTemplateFiles().keySet()) { String suffix = config.modelTestTemplateFiles().get(templateName); String filename = config.modelTestFileFolder() + File.separator + config.toModelTestFilename(modelName) + suffix; - // do not overwrite test file that already exists - if (new File(filename).exists()) { - LOGGER.info("File exists. Skipped overwriting " + filename); - continue; - } - File written = processTemplateToFile(models, templateName, filename); - if (written != null) { - files.add(written); - if (config.isEnablePostProcessFile()) { - config.postProcessFile(written, "model-test"); + + if (generateModelTests) { + // do not overwrite test file that already exists (regardless of config's skipOverwrite setting) + if (new File(filename).exists()) { + LOGGER.info("File exists. Skipped overwriting {}", filename); + if (dryRun) { + dryRunStatusMap.put(filename, + new DryRunStatus( + java.nio.file.Paths.get(filename), + DryRunStatus.State.SkippedOverwrite, + "Test files never overwrite an existing file of the same name." + )); + } + continue; } + File written = processTemplateToFile(models, templateName, filename); + if (written != null) { + files.add(written); + if (config.isEnablePostProcessFile() && !dryRun) { + config.postProcessFile(written, "model-test"); + } + } + } else if (dryRun) { + dryRunStatusMap.put(filename, + new DryRunStatus( + java.nio.file.Paths.get(filename), + DryRunStatus.State.Skipped, + "Skipped by modelTests option supplied by user." + )); } } } @@ -305,57 +336,86 @@ public class DefaultGenerator extends AbstractGenerator implements Generator { String docExtension = config.getDocExtension(); String suffix = docExtension != null ? docExtension : config.modelDocTemplateFiles().get(templateName); String filename = config.modelDocFileFolder() + File.separator + config.toModelDocFilename(modelName) + suffix; - if (!config.shouldOverwrite(filename)) { - LOGGER.info("Skipped overwriting " + filename); - continue; - } - File written = processTemplateToFile(models, templateName, filename); - if (written != null) { - files.add(written); - if (config.isEnablePostProcessFile()) { - config.postProcessFile(written, "model-doc"); + + if (generateModelDocumentation) { + if (!config.shouldOverwrite(filename)) { + LOGGER.info("Skipped overwriting {}", filename); + if (dryRun) { + dryRunStatusMap.put(filename, new DryRunStatus(java.nio.file.Paths.get(filename), DryRunStatus.State.SkippedOverwrite)); + } + continue; } + File written = processTemplateToFile(models, templateName, filename); + if (written != null) { + files.add(written); + if (config.isEnablePostProcessFile() && !dryRun) { + config.postProcessFile(written, "model-doc"); + } + } + } else if (dryRun) { + dryRunStatusMap.put(filename, + new DryRunStatus( + java.nio.file.Paths.get(filename), + DryRunStatus.State.Skipped, + "Skipped by modelDocs option supplied by user." + )); } } } + private String getModelFilenameByTemplate(String modelName, String templateName){ + String suffix = config.modelTemplateFiles().get(templateName); + return config.modelFileFolder() + File.separator + config.toModelFilename(modelName) + suffix; + } + private void generateModel(List files, Map models, String modelName) throws IOException { for (String templateName : config.modelTemplateFiles().keySet()) { - String suffix = config.modelTemplateFiles().get(templateName); - String filename = config.modelFileFolder() + File.separator + config.toModelFilename(modelName) + suffix; + String filename = getModelFilenameByTemplate(modelName, templateName); if (!config.shouldOverwrite(filename)) { - LOGGER.info("Skipped overwriting " + filename); + LOGGER.info("Skipped overwriting {}", filename); + if (dryRun) { + dryRunStatusMap.put(filename, new DryRunStatus( + java.nio.file.Paths.get(filename), + DryRunStatus.State.SkippedOverwrite + )); + } continue; } File written = processTemplateToFile(models, templateName, filename); if (written != null) { files.add(written); - if (config.isEnablePostProcessFile()) { + if (config.isEnablePostProcessFile() && !dryRun) { config.postProcessFile(written, "model"); } + } else { + LOGGER.warn("Unknown issue writing {}", filename); } } } + @SuppressWarnings("unchecked") private void generateModels(List files, List allModels, List unusedModels) { if (!generateModels) { + // TODO: Process these anyway and add to dryRun info + LOGGER.info("Skipping generation of models."); return; } final Map schemas = ModelUtils.getSchemas(this.openAPI); if (schemas == null) { + LOGGER.warn("Skipping generation of models because specification document has no schemas."); return; } String modelNames = GlobalSettings.getProperty("models"); Set modelsToGenerate = null; if (modelNames != null && !modelNames.isEmpty()) { - modelsToGenerate = new HashSet(Arrays.asList(modelNames.split(","))); + modelsToGenerate = new HashSet<>(Arrays.asList(modelNames.split(","))); } Set modelKeys = schemas.keySet(); if (modelsToGenerate != null && !modelsToGenerate.isEmpty()) { - Set updatedKeys = new HashSet(); + Set updatedKeys = new HashSet<>(); for (String m : modelKeys) { if (modelsToGenerate.contains(m)) { updatedKeys.add(m); @@ -366,59 +426,7 @@ public class DefaultGenerator extends AbstractGenerator implements Generator { } // store all processed models - Map allProcessedModels = new TreeMap(new Comparator() { - @Override - public int compare(String o1, String o2) { - return ObjectUtils.compare(config.toModelName(o1), config.toModelName(o2)); - } - /* TODO need to revise the logic below - - Model model1 = definitions.get(o1); - Model model2 = definitions.get(o2); - - int model1InheritanceDepth = getInheritanceDepth(model1); - int model2InheritanceDepth = getInheritanceDepth(model2); - - if (model1InheritanceDepth == model2InheritanceDepth) { - return ObjectUtils.compare(config.toModelName(o1), config.toModelName(o2)); - } else if (model1InheritanceDepth > model2InheritanceDepth) { - return 1; - } else { - return -1; - } - } - - private int getInheritanceDepth(Model model) { - int inheritanceDepth = 0; - Model parent = getParent(model); - - while (parent != null) { - inheritanceDepth++; - parent = getParent(parent); - } - - return inheritanceDepth; - } - - private Model getParent(Model model) { - if (model instanceof ComposedModel) { - Model parent = ((ComposedModel) model).getParent(); - if (parent == null) { - // check for interfaces - List interfaces = ((ComposedModel) model).getInterfaces(); - if (interfaces.size() > 0) { - RefModel interf = interfaces.get(0); - return definitions.get(interf.getSimpleRef()); - } - } - if (parent != null) { - return definitions.get(parent.getReference()); - } - } - - return null; - } */ - }); + Map allProcessedModels = new TreeMap<>((o1, o2) -> ObjectUtils.compare(config.toModelName(o1), config.toModelName(o2))); Boolean skipFormModel = GlobalSettings.getProperty(CodegenConstants.SKIP_FORM_MODEL) != null ? Boolean.valueOf(GlobalSettings.getProperty(CodegenConstants.SKIP_FORM_MODEL)) : @@ -429,7 +437,18 @@ public class DefaultGenerator extends AbstractGenerator implements Generator { try { //don't generate models that have an import mapping if (config.importMapping().containsKey(name)) { - LOGGER.debug("Model " + name + " not imported due to import mapping"); + LOGGER.debug("Model {} not imported due to import mapping", name); + if (dryRun) { + // HACK: Because this returns early, could lead to some invalid model reporting. + for (String templateName : config.modelTemplateFiles().keySet()) { + String filename = getModelFilenameByTemplate(name, templateName); + dryRunStatusMap.put(filename, new DryRunStatus( + java.nio.file.Paths.get(filename), + DryRunStatus.State.Skipped, + "Skipped prior to model processing due to import mapping conflict (either by user or by generator)." + )); + } + } continue; } @@ -437,9 +456,10 @@ public class DefaultGenerator extends AbstractGenerator implements Generator { if (unusedModels.contains(name)) { if (Boolean.FALSE.equals(skipFormModel)) { // if skipFormModel sets to true, still generate the model and log the result - LOGGER.info("Model " + name + " (marked as unused due to form parameters) is generated due to the system property skipFormModel=false (default)"); + LOGGER.info("Model {} (marked as unused due to form parameters) is generated due to the system property skipFormModel=false (default)", name); } else { - LOGGER.info("Model " + name + " not generated since it's marked as unused (due to form parameters) and skipFormModel (system property) set to true"); + LOGGER.info("Model {} not generated since it's marked as unused (due to form parameters) and skipFormModel (system property) set to true", name); + // TODO: Should this be added to dryRun? If not, this seems like a weird place to return early from processing. continue; } } @@ -447,7 +467,7 @@ public class DefaultGenerator extends AbstractGenerator implements Generator { Schema schema = schemas.get(name); if (ModelUtils.isFreeFormObject(schema)) { // check to see if it'a a free-form object - LOGGER.info("Model " + name + " not generated since it's a free-form object"); + LOGGER.info("Model {} not generated since it's a free-form object", name); continue; } else if (ModelUtils.isMapSchema(schema)) { // check to see if it's a "map" model // A composed schema (allOf, oneOf, anyOf) is considered a Map schema if the additionalproperties attribute is set @@ -455,13 +475,13 @@ public class DefaultGenerator extends AbstractGenerator implements Generator { // in the inner schemas, and the outer schema does not have properties. if (!ModelUtils.isGenerateAliasAsModel() && !ModelUtils.isComposedSchema(schema) && (schema.getProperties() == null || schema.getProperties().isEmpty())) { // schema without property, i.e. alias to map - LOGGER.info("Model " + name + " not generated since it's an alias to map (without property) and `generateAliasAsModel` is set to false (default)"); + LOGGER.info("Model {} not generated since it's an alias to map (without property) and `generateAliasAsModel` is set to false (default)", name); continue; } } else if (ModelUtils.isArraySchema(schema)) { // check to see if it's an "array" model if (!ModelUtils.isGenerateAliasAsModel() && (schema.getProperties() == null || schema.getProperties().isEmpty())) { // schema without property, i.e. alias to array - LOGGER.info("Model " + name + " not generated since it's an alias to array (without property) and `generateAliasAsModel` is set to false (default)"); + LOGGER.info("Model {} not generated since it's an alias to array (without property) and `generateAliasAsModel` is set to false (default)", name); continue; } } @@ -509,14 +529,12 @@ public class DefaultGenerator extends AbstractGenerator implements Generator { // to generate model files generateModel(files, models, modelName); - if (generateModelTests) { - // to generate model test files - generateModelTests(files, models, modelName); - } - if (generateModelDocumentation) { - // to generate model documentation files - generateModelDocumentation(files, models, modelName); - } + // to generate model test files + generateModelTests(files, models, modelName); + + // to generate model documentation files + generateModelDocumentation(files, models, modelName); + } catch (Exception e) { throw new RuntimeException("Could not generate model '" + modelName + "'", e); } @@ -528,18 +546,21 @@ public class DefaultGenerator extends AbstractGenerator implements Generator { } + @SuppressWarnings("unchecked") private void generateApis(List files, List allOperations, List allModels) { if (!generateApis) { + // TODO: Process these anyway and present info via dryRun? + LOGGER.info("Skipping generation of APIs."); return; } Map> paths = processPaths(this.openAPI.getPaths()); Set apisToGenerate = null; String apiNames = GlobalSettings.getProperty("apis"); if (apiNames != null && !apiNames.isEmpty()) { - apisToGenerate = new HashSet(Arrays.asList(apiNames.split(","))); + apisToGenerate = new HashSet<>(Arrays.asList(apiNames.split(","))); } if (apisToGenerate != null && !apisToGenerate.isEmpty()) { - Map> updatedPaths = new TreeMap>(); + Map> updatedPaths = new TreeMap<>(); for (String m : paths.keySet()) { if (apisToGenerate.contains(m)) { updatedPaths.put(m, paths.get(m)); @@ -550,12 +571,7 @@ public class DefaultGenerator extends AbstractGenerator implements Generator { for (String tag : paths.keySet()) { try { List ops = paths.get(tag); - Collections.sort(ops, new Comparator() { - @Override - public int compare(CodegenOperation one, CodegenOperation another) { - return ObjectUtils.compare(one.operationId, another.operationId); - } - }); + ops.sort((one, another) -> ObjectUtils.compare(one.operationId, another.operationId)); Map operation = processOperations(config, tag, ops, allModels); URL url = URLPathUtils.getServerURL(openAPI, config.serverVariableOverrides()); operation.put("basePath", basePath); @@ -583,7 +599,7 @@ public class DefaultGenerator extends AbstractGenerator implements Generator { // process top-level x-group-parameters if (config.vendorExtensions().containsKey("x-group-parameters")) { - Boolean isGroupParameters = Boolean.valueOf(config.vendorExtensions().get("x-group-parameters").toString()); + boolean isGroupParameters = Boolean.parseBoolean(config.vendorExtensions().get("x-group-parameters").toString()); Map objectMap = (Map) operation.get("operations"); @SuppressWarnings("unchecked") @@ -598,7 +614,7 @@ public class DefaultGenerator extends AbstractGenerator implements Generator { // Pass sortParamsByRequiredFlag through to the Mustache template... boolean sortParamsByRequiredFlag = true; if (this.config.additionalProperties().containsKey(CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG)) { - sortParamsByRequiredFlag = Boolean.valueOf(this.config.additionalProperties().get(CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG).toString()); + sortParamsByRequiredFlag = Boolean.parseBoolean(this.config.additionalProperties().get(CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG).toString()); } operation.put("sortParamsByRequiredFlag", sortParamsByRequiredFlag); @@ -607,7 +623,7 @@ public class DefaultGenerator extends AbstractGenerator implements Generator { processMimeTypes(swagger.getProduces(), operation, "produces"); */ - allOperations.add(new HashMap(operation)); + allOperations.add(new HashMap<>(operation)); for (int i = 0; i < allOperations.size(); i++) { Map oo = (Map) allOperations.get(i); if (i < (allOperations.size() - 1)) { @@ -617,56 +633,81 @@ public class DefaultGenerator extends AbstractGenerator implements Generator { for (String templateName : config.apiTemplateFiles().keySet()) { String filename = config.apiFilename(templateName, tag); - if (!config.shouldOverwrite(filename) && new File(filename).exists()) { - LOGGER.info("Skipped overwriting " + filename); + File apiFile = new File(filename); + if (!config.shouldOverwrite(filename) && apiFile.exists()) { + LOGGER.info("Skipped overwriting {}", filename); + if (dryRun) { + DryRunStatus status = new DryRunStatus(apiFile.toPath(), DryRunStatus.State.SkippedOverwrite); + dryRunStatusMap.put(filename, status); + } continue; } File written = processTemplateToFile(operation, templateName, filename); if (written != null) { files.add(written); - if (config.isEnablePostProcessFile()) { + if (config.isEnablePostProcessFile() && !dryRun) { config.postProcessFile(written, "api"); } } } - if (generateApiTests) { - // to generate api test files - for (String templateName : config.apiTestTemplateFiles().keySet()) { - String filename = config.apiTestFilename(templateName, tag); + // to generate api test files + for (String templateName : config.apiTestTemplateFiles().keySet()) { + String filename = config.apiTestFilename(templateName, tag); + File apiTestFile = new File(filename); + if (generateApiTests) { // do not overwrite test file that already exists - if (new File(filename).exists()) { - LOGGER.info("File exists. Skipped overwriting " + filename); + if (apiTestFile.exists()) { + LOGGER.info("File exists. Skipped overwriting {}", filename); + if (dryRun) { + dryRunStatusMap.put(filename, new DryRunStatus(apiTestFile.toPath(), DryRunStatus.State.SkippedOverwrite)); + } continue; } File written = processTemplateToFile(operation, templateName, filename); if (written != null) { files.add(written); - if (config.isEnablePostProcessFile()) { + if (config.isEnablePostProcessFile() && !dryRun) { config.postProcessFile(written, "api-test"); } } + } else if (dryRun) { + dryRunStatusMap.put(filename, new DryRunStatus( + apiTestFile.toPath(), + DryRunStatus.State.Skipped, + "Skipped by apiTests option supplied by user." + )); } } - if (generateApiDocumentation) { - // to generate api documentation files - for (String templateName : config.apiDocTemplateFiles().keySet()) { - String filename = config.apiDocFilename(templateName, tag); - if (!config.shouldOverwrite(filename) && new File(filename).exists()) { - LOGGER.info("Skipped overwriting " + filename); + // to generate api documentation files + for (String templateName : config.apiDocTemplateFiles().keySet()) { + String filename = config.apiDocFilename(templateName, tag); + File apiDocFile = new File(filename); + if (generateApiDocumentation) { + if (!config.shouldOverwrite(filename) && apiDocFile.exists()) { + LOGGER.info("Skipped overwriting {}", filename); + if (dryRun) { + dryRunStatusMap.put(filename, new DryRunStatus(apiDocFile.toPath(), DryRunStatus.State.SkippedOverwrite)); + } continue; } File written = processTemplateToFile(operation, templateName, filename); if (written != null) { files.add(written); - if (config.isEnablePostProcessFile()) { + if (config.isEnablePostProcessFile() && !dryRun) { config.postProcessFile(written, "api-doc"); } } + } else if (dryRun) { + dryRunStatusMap.put(filename, new DryRunStatus( + apiDocFile.toPath(), + DryRunStatus.State.Skipped, + "Skipped by apiDocs option supplied by user." + )); } } @@ -683,12 +724,14 @@ public class DefaultGenerator extends AbstractGenerator implements Generator { private void generateSupportingFiles(List files, Map bundle) { if (!generateSupportingFiles) { + // TODO: process these anyway and report via dryRun? + LOGGER.info("Skipping generation of supporting files."); return; } Set supportingFilesToGenerate = null; String supportingFiles = GlobalSettings.getProperty(CodegenConstants.SUPPORTING_FILES); if (supportingFiles != null && !supportingFiles.isEmpty()) { - supportingFilesToGenerate = new HashSet(Arrays.asList(supportingFiles.split(","))); + supportingFilesToGenerate = new HashSet<>(Arrays.asList(supportingFiles.split(","))); } for (SupportingFile support : config.supportingFiles()) { @@ -699,13 +742,22 @@ public class DefaultGenerator extends AbstractGenerator implements Generator { } File of = new File(outputFolder); if (!of.isDirectory()) { - of.mkdirs(); + if(!dryRun && !of.mkdirs()) { + once(LOGGER).debug("Output directory {} not created. It {}.", outputFolder, of.exists() ? "already exists." : "may not have appropriate permissions."); + } } String outputFilename = new File(support.destinationFilename).isAbsolute() // split ? support.destinationFilename : outputFolder + File.separator + support.destinationFilename.replace('/', File.separatorChar); if (!config.shouldOverwrite(outputFilename)) { - LOGGER.info("Skipped overwriting " + outputFilename); + LOGGER.info("Skipped overwriting {}", outputFilename); + if (dryRun) { + Path skippedSupportingFile = java.nio.file.Paths.get(outputFilename); + DryRunStatus status = new DryRunStatus( + skippedSupportingFile, + DryRunStatus.State.SkippedOverwrite + ); + } continue; } String templateFile; @@ -719,6 +771,15 @@ public class DefaultGenerator extends AbstractGenerator implements Generator { shouldGenerate = supportingFilesToGenerate.contains(support.destinationFilename); } if (!shouldGenerate) { + if (dryRun) { + Path skippedSupportingFile = java.nio.file.Paths.get(outputFilename); + DryRunStatus status = new DryRunStatus( + skippedSupportingFile, + DryRunStatus.State.Skipped, + "Skipped by supportingFiles option supplied by user." + ); + dryRunStatusMap.put(outputFilename, status); + } continue; } @@ -744,12 +805,16 @@ public class DefaultGenerator extends AbstractGenerator implements Generator { } File outputFile = writeInputStreamToFile(outputFilename, in, templateFile); files.add(outputFile); - if (config.isEnablePostProcessFile()) { + if (config.isEnablePostProcessFile() && !dryRun) { config.postProcessFile(outputFile, "supporting-common"); } } + } else { - LOGGER.info("Skipped generation of " + outputFilename + " due to rule in .openapi-generator-ignore"); + if (dryRun) { + dryRunStatusMap.put(outputFilename, new DryRunStatus(java.nio.file.Paths.get(outputFilename), DryRunStatus.State.Ignored)); + } + LOGGER.info("Skipped generation of {} due to rule in .openapi-generator-ignore", outputFilename); } } catch (Exception e) { throw new RuntimeException("Could not generate supporting file '" + support + "'", e); @@ -770,23 +835,35 @@ public class DefaultGenerator extends AbstractGenerator implements Generator { throw new RuntimeException("Could not generate supporting file '" + openapiGeneratorIgnore + "'", e); } files.add(ignoreFile); - if (config.isEnablePostProcessFile()) { + if (config.isEnablePostProcessFile() && !dryRun) { config.postProcessFile(ignoreFile, "openapi-generator-ignore"); } + } else if (generateMetadata && dryRun && ignoreFile.exists()) { + dryRunStatusMap.put(ignoreFileNameTarget, new DryRunStatus(ignoreFile.toPath(), DryRunStatus.State.SkippedOverwrite)); + } else if (!generateMetadata && dryRun) { + dryRunStatusMap.put(ignoreFileNameTarget, new DryRunStatus( + ignoreFile.toPath(), + DryRunStatus.State.Skipped, + "Skipped by generateMetadata option supplied by user" + )); } + String versionMetadata = config.outputFolder() + File.separator + ".openapi-generator" + File.separator + "VERSION"; if (generateMetadata) { - final String versionMetadata = config.outputFolder() + File.separator + ".openapi-generator" + File.separator + "VERSION"; File versionMetadataFile = new File(versionMetadata); try { writeToFile(versionMetadata, ImplementationVersion.read()); files.add(versionMetadataFile); - if (config.isEnablePostProcessFile()) { + if (config.isEnablePostProcessFile() && !dryRun) { config.postProcessFile(ignoreFile, "openapi-generator-version"); } } catch (IOException e) { throw new RuntimeException("Could not generate supporting file '" + versionMetadata + "'", e); } + } else if(!generateMetadata && dryRun) { + Path metadata = java.nio.file.Paths.get(versionMetadata); + DryRunStatus status = new DryRunStatus(metadata, DryRunStatus.State.Skipped, "Skipped by generateMetadata option supplied by user."); + dryRunStatusMap.put(versionMetadata, status); } /* @@ -809,23 +886,13 @@ public class DefaultGenerator extends AbstractGenerator implements Generator { } - protected File writeInputStreamToFile(String filename, InputStream in, String templateFile) throws FileNotFoundException, IOException { - if (in != null) { - byte bytes[] = IOUtils.toByteArray(in); - return writeToFile(filename, bytes); - } else { - LOGGER.error("can't open '" + templateFile + "' for input; cannot write '" + filename + "'"); - return null; - } - } - + @SuppressWarnings("unchecked") private Map buildSupportFileBundle(List allOperations, List allModels) { - Map bundle = new HashMap(); - bundle.putAll(config.additionalProperties()); + Map bundle = new HashMap<>(config.additionalProperties()); bundle.put("apiPackage", config.apiPackage()); - Map apis = new HashMap(); + Map apis = new HashMap<>(); apis.put("apis", allOperations); URL url = URLPathUtils.getServerURL(openAPI, config.serverVariableOverrides()); @@ -903,7 +970,7 @@ public class DefaultGenerator extends AbstractGenerator implements Generator { } if (config.getGeneratorMetadata() == null) { - LOGGER.warn(String.format(Locale.ROOT, "Generator '%s' is missing generator metadata!", config.getName())); + LOGGER.warn("Generator '{}' is missing generator metadata!", config.getName()); } else { GeneratorMetadata generatorMetadata = config.getGeneratorMetadata(); if (StringUtils.isNotEmpty(generatorMetadata.getGenerationMessage())) { @@ -929,13 +996,13 @@ public class DefaultGenerator extends AbstractGenerator implements Generator { // If the template adapter is mustache, we'll set the config-modified Compiler. configPostProcessMustacheCompiler(); - List files = new ArrayList(); + List files = new ArrayList<>(); // models List filteredSchemas = ModelUtils.getSchemasUsedOnlyInFormParam(openAPI); - List allModels = new ArrayList(); + List allModels = new ArrayList<>(); generateModels(files, allModels, filteredSchemas); // apis - List allOperations = new ArrayList(); + List allOperations = new ArrayList<>(); generateApis(files, allOperations, allModels); // supporting files @@ -943,6 +1010,43 @@ public class DefaultGenerator extends AbstractGenerator implements Generator { generateSupportingFiles(files, bundle); config.processOpenAPI(openAPI); + if(dryRun) { + boolean verbose = Boolean.parseBoolean(GlobalSettings.getProperty("verbose")); + StringBuilder sb = new StringBuilder(); + + sb.append(System.lineSeparator()).append(System.lineSeparator()); + sb.append("Dry Run Results:"); + sb.append(System.lineSeparator()).append(System.lineSeparator()); + + dryRunStatusMap.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(entry -> { + DryRunStatus status = entry.getValue(); + try { + status.appendTo(sb); + sb.append(System.lineSeparator()); + if (verbose) { + sb.append(" ") + .append(StringUtils.rightPad(status.getState().getDescription(), 20, ".")) + .append(" ").append(status.getReason()) + .append(System.lineSeparator()); + } + } catch (IOException e) { + LOGGER.debug("Unable to document dry run status for {}.", entry.getKey()); + } + }); + + sb.append(System.lineSeparator()).append(System.lineSeparator()); + sb.append("States:"); + sb.append(System.lineSeparator()).append(System.lineSeparator()); + + for (DryRunStatus.State state : DryRunStatus.State.values()) { + sb.append(" - ").append(state.getShortDisplay()).append(" ").append(state.getDescription()).append(System.lineSeparator()); + } + + sb.append(System.lineSeparator()); + + System.err.println(sb.toString()); + } + // reset GlobalSettings, so that the running thread can be reused for another generator-run GlobalSettings.reset(); @@ -968,18 +1072,22 @@ public class DefaultGenerator extends AbstractGenerator implements Generator { protected File processTemplateToFile(Map templateData, String templateName, String outputFilename) throws IOException { String adjustedOutputFilename = outputFilename.replaceAll("//", "/").replace('/', File.separatorChar); - if (ignoreProcessor.allowsFile(new File(adjustedOutputFilename))) { + File target = new File(adjustedOutputFilename); + if (ignoreProcessor.allowsFile(target)) { String templateContent = templatingEngine.compileTemplate(this, templateData, templateName); writeToFile(adjustedOutputFilename, templateContent); - return new File(adjustedOutputFilename); + return target; + } else if (this.dryRun) { + dryRunStatusMap.put(adjustedOutputFilename, new DryRunStatus(target.toPath(), DryRunStatus.State.Ignored)); + return target; } - LOGGER.info("Skipped generation of " + adjustedOutputFilename + " due to rule in .openapi-generator-ignore"); + LOGGER.info("Skipped generation of {} due to rule in .openapi-generator-ignore", adjustedOutputFilename); return null; } public Map> processPaths(Paths paths) { - Map> ops = new TreeMap>(); + Map> ops = new TreeMap<>(); for (String resourcePath : paths.keySet()) { PathItem path = paths.get(resourcePath); processOperation(resourcePath, "get", path.getGet(), ops, path); @@ -1000,10 +1108,10 @@ public class DefaultGenerator extends AbstractGenerator implements Generator { } if (GlobalSettings.getProperty("debugOperations") != null) { - LOGGER.info("processOperation: resourcePath= " + resourcePath + "\t;" + httpMethod + " " + operation + "\n"); + LOGGER.info("processOperation: resourcePath= {}\t;{} {}\n", resourcePath, httpMethod, operation); } - List tags = new ArrayList(); + List tags = new ArrayList<>(); List tagNames = operation.getTags(); List swaggerTags = openAPI.getTags(); if (tagNames != null) { @@ -1038,7 +1146,7 @@ public class DefaultGenerator extends AbstractGenerator implements Generator { per the swagger 2.0 spec "A unique parameter is defined by a combination of a name and location" i'm assuming "location" == "in" */ - Set operationParameters = new HashSet(); + Set operationParameters = new HashSet<>(); if (operation.getParameters() != null) { for (Parameter parameter : operation.getParameters()) { operationParameters.add(generateParameterId(parameter)); @@ -1055,7 +1163,6 @@ public class DefaultGenerator extends AbstractGenerator implements Generator { } } - final Map schemas = openAPI.getComponents() != null ? openAPI.getComponents().getSchemas() : null; final Map securitySchemes = openAPI.getComponents() != null ? openAPI.getComponents().getSecuritySchemes() : null; final List globalSecurities = openAPI.getSecurity(); for (Tag tag : tags) { @@ -1102,14 +1209,15 @@ public class DefaultGenerator extends AbstractGenerator implements Generator { return parameter.getName() + ":" + parameter.getIn(); } + @SuppressWarnings("unchecked") private Map processOperations(CodegenConfig config, String tag, List ops, List allModels) { - Map operations = new HashMap(); - Map objs = new HashMap(); + Map operations = new HashMap<>(); + Map objs = new HashMap<>(); objs.put("classname", config.toApiName(tag)); objs.put("pathPrefix", config.toApiVarName(tag)); // check for operationId uniqueness - Set opIds = new HashSet(); + Set opIds = new HashSet<>(); int counter = 0; for (CodegenOperation op : ops) { String opId = op.nickname; @@ -1124,15 +1232,15 @@ public class DefaultGenerator extends AbstractGenerator implements Generator { operations.put("operations", objs); operations.put("package", config.apiPackage()); - Set allImports = new TreeSet(); + Set allImports = new TreeSet<>(); for (CodegenOperation op : ops) { allImports.addAll(op.imports); } - List> imports = new ArrayList>(); + List> imports = new ArrayList<>(); Set mappingSet = new TreeSet<>(); for (String nextImport : allImports) { - Map im = new LinkedHashMap(); + Map im = new LinkedHashMap<>(); String mapping = config.importMapping().get(nextImport); if (mapping == null) { mapping = config.toModelImport(nextImport); @@ -1168,16 +1276,16 @@ public class DefaultGenerator extends AbstractGenerator implements Generator { } private Map processModels(CodegenConfig config, Map definitions) { - Map objs = new HashMap(); + Map objs = new HashMap<>(); objs.put("package", config.modelPackage()); - List models = new ArrayList(); - Set allImports = new LinkedHashSet(); + List models = new ArrayList<>(); + Set allImports = new LinkedHashSet<>(); for (String key : definitions.keySet()) { Schema schema = definitions.get(key); if (schema == null) throw new RuntimeException("schema cannot be null in processModels"); CodegenModel cm = config.fromModel(key, schema); - Map mo = new HashMap(); + Map mo = new HashMap<>(); mo.put("model", cm); mo.put("importPath", config.toModelImport(cm.classname)); models.add(mo); @@ -1187,7 +1295,7 @@ public class DefaultGenerator extends AbstractGenerator implements Generator { allImports.addAll(cm.imports); } objs.put("models", models); - Set importSet = new TreeSet(); + Set importSet = new TreeSet<>(); for (String nextImport : allImports) { String mapping = config.importMapping().get(nextImport); if (mapping == null) { @@ -1202,9 +1310,9 @@ public class DefaultGenerator extends AbstractGenerator implements Generator { importSet.add(mapping); } } - List> imports = new ArrayList>(); + List> imports = new ArrayList<>(); for (String s : importSet) { - Map item = new HashMap(); + Map item = new HashMap<>(); item.put("import", s); imports.add(item); } @@ -1295,7 +1403,7 @@ public class DefaultGenerator extends AbstractGenerator implements Generator { return authMethods; } - List result = new ArrayList(); + List result = new ArrayList<>(); for (CodegenSecurity security : authMethods) { boolean filtered = false; @@ -1356,4 +1464,51 @@ public class DefaultGenerator extends AbstractGenerator implements Generator { return oauthMethods; } + + protected File writeInputStreamToFile(String filename, InputStream in, String templateFile) throws IOException { + if (in != null) { + byte[] bytes = IOUtils.toByteArray(in); + if (dryRun) { + Path path = java.nio.file.Paths.get(filename); + dryRunStatusMap.put(filename, new DryRunStatus(path)); + return path.toFile(); + } + + return writeToFile(filename, bytes); + } else { + LOGGER.error("can't open '{}' for input; cannot write '{}'", templateFile, filename); + if (dryRun) { + Path path = java.nio.file.Paths.get(filename); + dryRunStatusMap.put(filename, new DryRunStatus(path, DryRunStatus.State.Error)); + } + + return null; + } + } + + /** + * Write bytes to a file + * + * @param filename The name of file to write + * @param contents The contents bytes. Typically this is a UTF-8 formatted string. + * @return File representing the written file. + * @throws IOException If file cannot be written. + */ + @Override + public File writeToFile(String filename, byte[] contents) throws IOException { + if (dryRun) { + Path path = java.nio.file.Paths.get(filename); + DryRunStatus status = new DryRunStatus(path); + if (getEnableMinimalUpdate()) { + status.setState(DryRunStatus.State.WriteIfNewer); + } else { + status.setState(DryRunStatus.State.Write); + } + + dryRunStatusMap.put(filename, status); + return path.toFile(); + } else { + return super.writeToFile(filename, contents); + } + } } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DryRunStatus.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DryRunStatus.java new file mode 100644 index 00000000000..bd755e3a83d --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DryRunStatus.java @@ -0,0 +1,160 @@ +package org.openapitools.codegen; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Locale; + +/** + * Holds details about a file's write status for display via the --dry-run option of CLI + */ +class DryRunStatus { + private Path path; + private State state; + private String reason; + + /** + * Constructs a new instance of {@link DryRunStatus} for a given path and status of {@link State#Write} + * + * @param path The target path where the file would write + */ + public DryRunStatus(Path path) { + this(path, State.Write); + } + + /** + * Constructs a new instance of {@link DryRunStatus} for a path and a target state + * + * @param path The target path where the file would write + * @param state The evaluated state as determined by the generation workflow + */ + public DryRunStatus(Path path, State state) { + this.path = path; + setState(state); + } + + /** + * Constructs a new instance of {@link DryRunStatus} for a path, target state, and presenting a specific reason for that state + * + * @param path The target path where the file would write + * @param state The evaluated state as determined by the generation workflow + * @param reason A reason for the state, beyond any generic reason + */ + public DryRunStatus(Path path, State state, String reason) { + this.path = path; + this.state = state; + this.reason = reason; + } + + /** + * Append a user display text to the {@link Appendable} instance + * + * @param appendable An object implementing {@link Appendable} (such as {@link StringBuilder} + * @throws IOException via contract of {@link Path#toAbsolutePath()} + */ + public void appendTo(Appendable appendable) throws IOException { + appendable.append(String.format(Locale.ROOT, "%s %s", this.state.getShortDisplay(), this.path.toAbsolutePath())); + } + + /** + * Gets the target path of the file write operation + * + * @return a {@link Path} instance + */ + public Path getPath() { + return path; + } + + /** + * Gets the reason for the file's {@link State} + * + * @return A human-readable string which explains why this file's dry-run resulted in the defined {@link State} + */ + public String getReason() { + return reason; + } + + /** + * Gets the {@link State} as determined by the generator's workflow + * + * @return A {@link State} enum detailing the expected operation of the generator's workflow + */ + public State getState() { + return state; + } + + /** + * Sets the {@link State} as determined by the generator's workflow. + *

+ * This method will provide a default reason. To explicitly provide a reason for the {@link State}, use {@link DryRunStatus#DryRunStatus(Path, State, String)} + * + * @param state A {@link State} enum detailing the expected operation of the generator's workflow + */ + public void setState(State state) { + if (state != this.state) { + switch (state) { + case Write: + this.reason = "File will be written."; + break; + case WriteIfNewer: + this.reason = "File will be written only if it is new or if contents differ from an existing file."; + break; + case Ignored: + this.reason = "Ignored via rules defined in codegen ignore file."; + break; + case SkippedOverwrite: + this.reason = "File is configured not to overwrite an existing file of the same name."; + break; + case Error: + this.reason = "File error: template does not exist, or file is not accessible."; + break; + } + } + + this.state = state; + } + + /** + * Represents the possible states of a file write operation as determined by the Generator + */ + enum State { + Write("w", "Write"), + WriteIfNewer("n", "Write if New/Updated"), + Ignored("i", "Ignored"), + SkippedOverwrite("s", "Skipped Overwrite"), + Skipped("k", "Skipped by user option(s)"), + Error("e", "Error evaluating file write state"); + + private final String shortDisplay; + private final String description; + + /** + * Constructs a new {@link State} with required short value and human-readable description + * + * @param shortDisplay The short value used for display + * @param description A description of the state which is more human-readable than the enum's name + */ + State(String shortDisplay, String description) { + + this.shortDisplay = shortDisplay; + this.description = description; + } + + /** + * Gets a description of the state which is more human-readable than the enum's name + * + * @return A human-readable description + */ + public String getDescription() { + return description; + } + + /** + * Gets the short value used for display + * + * @return A character representing this state + */ + public String getShortDisplay() { + return shortDisplay; + } + } +} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/config/CodegenConfigurator.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/config/CodegenConfigurator.java index 31cadc1e292..b2ad527e2f0 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/config/CodegenConfigurator.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/config/CodegenConfigurator.java @@ -439,6 +439,9 @@ public class CodegenConfigurator { GlobalSettings.setProperty("debugModels", ""); GlobalSettings.setProperty("debugOperations", ""); GlobalSettings.setProperty("debugSupportingFiles", ""); + GlobalSettings.setProperty("verbose", "true"); + } else { + GlobalSettings.setProperty("verbose", "false"); } for (Map.Entry entry : workflowSettings.getSystemProperties().entrySet()) {