From 1f45ea7d1a0b63da18b0f46e3a3b55a2ecc83eef Mon Sep 17 00:00:00 2001 From: dragosnutu <48376420+dragosnutu@users.noreply.github.com> Date: Mon, 18 Mar 2019 15:49:45 +0200 Subject: [PATCH] [#2425] - implemented yaml parsing for config file. (#2434) --- docs/customization.md | 15 +- docs/usage.md | 20 +- .../openapitools/codegen/cmd/Generate.java | 26 ++- .../codegen/cmd/GenerateTest.java | 22 ++- .../codegen/config/CodegenConfigurator.java | 181 +++++++++++------- 5 files changed, 181 insertions(+), 83 deletions(-) diff --git a/docs/customization.md b/docs/customization.md index c696f4a0442..bfecdecbd97 100644 --- a/docs/customization.md +++ b/docs/customization.md @@ -217,9 +217,13 @@ and `config.json` contains the following as an example: "apiPackage" : "petstore" } ``` +You can use also `config.yml` with following equivalent example: +```yaml +apiPackage: "petstore" +``` Supported config options can be different per language. Running `config-help -g {lang}` will show available options. -**These options are applied via configuration file (e.g. config.json) or by passing them with `-D{optionName}={optionValue}`**. (If `-D{optionName}` does not work, please open a [ticket](https://github.com/openapitools/openapi-generator/issues/new) and we'll look into it) +**These options are applied via configuration file (e.g. config.json or config.yml) or by passing them with `-D{optionName}={optionValue}`**. (If `-D{optionName}` does not work, please open a [ticket](https://github.com/openapitools/openapi-generator/issues/new) and we'll look into it) ```sh java -jar modules/openapi-generator-cli/target/openapi-generator-cli.jar config-help -g java @@ -258,6 +262,15 @@ Your config file for Java can look like } ``` +Or if you preffer yaml format it can look like + +```yaml +groupId: "com.my.company" +artifactId: "MyClient" +artifactVersion: "1.2.0" +library: "feign" +``` + For all the unspecified options default values will be used. Another way to override default options is to extend the config class for the specific language. diff --git a/docs/usage.md b/docs/usage.md index 284ba9e9f34..abd221e5c3a 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -480,7 +480,7 @@ NOTE: `import-mappings` is assigned a key-value pair in this example, but multip #### Configuration File -Rather than passing generator options in a CSV of `--additional-properties`, you may also provide the settings via JSON file. +Rather than passing generator options in a CSV of `--additional-properties`, you may also provide the settings via JSON file or YAML file. For example, one of our typescript samples has the following configuration file: @@ -500,3 +500,21 @@ These settings can be passed via `-c filename`. Here, we've saved the above as ` openapi-generator generate -i petstore.yaml -g typescript-fetch -o out \ -c config.json ``` + +Same configuration file can be passed into YAML format having following equivalent content: + +```yaml +npmName: "@swagger/typescript-fetch-petstore" +npmVersion: "1.0.0" +npmRepository: "https://skimdb.npmjs.com/registry" +snapshot: false +supportsES6: true +``` + +The settings are passed exactly the same as for `config.json`. The most important part is the file extension. Supported values are `yml` or `yaml`. +The name of the file should be `config.yml` or `config.yaml` (in our example it will be `config.yaml`. + +```bash +openapi-generator generate -i petstore.yaml -g typescript-fetch -o out \ + -c config.yaml +``` 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 8f108203945..f68e93ce29f 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 @@ -17,10 +17,23 @@ package org.openapitools.codegen.cmd; +import static org.apache.commons.lang3.StringUtils.isNotEmpty; +import static org.openapitools.codegen.config.CodegenConfiguratorUtils.applyAdditionalPropertiesKvpList; +import static org.openapitools.codegen.config.CodegenConfiguratorUtils.applyImportMappingsKvpList; +import static org.openapitools.codegen.config.CodegenConfiguratorUtils.applyInstantiationTypesKvpList; +import static org.openapitools.codegen.config.CodegenConfiguratorUtils.applyLanguageSpecificPrimitivesCsvList; +import static org.openapitools.codegen.config.CodegenConfiguratorUtils.applyReservedWordsMappingsKvpList; +import static org.openapitools.codegen.config.CodegenConfiguratorUtils.applySystemPropertiesKvpList; +import static org.openapitools.codegen.config.CodegenConfiguratorUtils.applyTypeMappingsKvpList; + import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.core.spi.FilterAttachable; import io.airlift.airline.Command; import io.airlift.airline.Option; +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; import org.openapitools.codegen.ClientOptInput; import org.openapitools.codegen.CodegenConstants; import org.openapitools.codegen.DefaultGenerator; @@ -29,14 +42,6 @@ import org.openapitools.codegen.config.CodegenConfigurator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static org.openapitools.codegen.config.CodegenConfiguratorUtils.*; -import static org.apache.commons.lang3.StringUtils.isNotEmpty; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Stream; - /** * User: lanwen Date: 24.03.15 Time: 20:22 */ @@ -82,8 +87,9 @@ public class Generate implements Runnable { @Option( name = {"-c", "--config"}, title = "configuration file", - description = "Path to json configuration file. " - + "File content should be in a json format {\"optionKey\":\"optionValue\", \"optionKey1\":\"optionValue1\"...} " + description = "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: optionValue" + "Supported options can be different for each language. Run config-help -g {generator name} command for language specific config options.") private String configFile; diff --git a/modules/openapi-generator-cli/src/test/java/org/openapitools/codegen/cmd/GenerateTest.java b/modules/openapi-generator-cli/src/test/java/org/openapitools/codegen/cmd/GenerateTest.java index dea73a301ef..2738eebfac9 100644 --- a/modules/openapi-generator-cli/src/test/java/org/openapitools/codegen/cmd/GenerateTest.java +++ b/modules/openapi-generator-cli/src/test/java/org/openapitools/codegen/cmd/GenerateTest.java @@ -218,7 +218,7 @@ public class GenerateTest { @Test - public void testConfig() throws Exception { + public void testConfigJson() throws Exception { setupAndRunTest("-i", "src/test/resources/swagger.yaml", "-g", "java", "-o", "src/main/java", true, "config.json", "-c", "config.json"); @@ -237,6 +237,26 @@ public class GenerateTest { }; } + @Test + public void testConfigYaml() throws Exception { + + setupAndRunTest("-i", "src/test/resources/swagger.yaml", "-g", "java", "-o", "src/main/java", true, + "config.yaml", "-c", "config.yaml"); + + new FullVerifications() { + { + } + }; + + setupAndRunTest("-i", "src/test/resources/swagger.yaml", "-g", "java", "-o", "src/main/java", true, + "config.yaml", "--config", "config.yaml"); + + new FullVerifications() { + { + } + }; + } + @Test public void testSkipOverwrite() throws Exception { 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 a1bdb827ea5..64aa97b280f 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 @@ -17,41 +17,66 @@ package org.openapitools.codegen.config; +import static org.apache.commons.lang3.StringUtils.isNotEmpty; + import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.databind.ObjectMapper; import io.swagger.parser.OpenAPIParser; import io.swagger.v3.core.util.Json; +import io.swagger.v3.core.util.Yaml; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.parser.core.models.AuthorizationValue; import io.swagger.v3.parser.core.models.ParseOptions; import io.swagger.v3.parser.core.models.SwaggerParseResult; -import org.apache.commons.lang3.Validate; -import org.openapitools.codegen.*; -import org.openapitools.codegen.auth.AuthParser; -import org.openapitools.codegen.languages.*; -import org.openapitools.codegen.utils.ModelUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.File; import java.io.IOException; import java.io.Serializable; import java.nio.file.Paths; -import java.util.*; - -import static org.apache.commons.lang3.StringUtils.isNotEmpty; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.Validate; +import org.openapitools.codegen.CliOption; +import org.openapitools.codegen.ClientOptInput; +import org.openapitools.codegen.ClientOpts; +import org.openapitools.codegen.CodegenConfig; +import org.openapitools.codegen.CodegenConfigLoader; +import org.openapitools.codegen.CodegenConstants; +import org.openapitools.codegen.SpecValidationException; +import org.openapitools.codegen.auth.AuthParser; +import org.openapitools.codegen.languages.CSharpNancyFXServerCodegen; +import org.openapitools.codegen.languages.CppQt5ClientCodegen; +import org.openapitools.codegen.languages.CppRestSdkClientCodegen; +import org.openapitools.codegen.languages.CppTizenClientCodegen; +import org.openapitools.codegen.languages.JavaJerseyServerCodegen; +import org.openapitools.codegen.languages.PhpLumenServerCodegen; +import org.openapitools.codegen.languages.PhpSlimServerCodegen; +import org.openapitools.codegen.languages.PhpZendExpressivePathHandlerServerCodegen; +import org.openapitools.codegen.languages.RubySinatraServerCodegen; +import org.openapitools.codegen.languages.ScalaAkkaClientCodegen; +import org.openapitools.codegen.languages.ScalaHttpClientCodegen; +import org.openapitools.codegen.languages.SwiftClientCodegen; +import org.openapitools.codegen.utils.ModelUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** - * A class that contains all codegen configuration properties a user would want to manipulate. - * An instance could be created by deserializing a JSON file or being populated from CLI or Maven plugin parameters. - * It also has a convenience method for creating a ClientOptInput class which is THE object DefaultGenerator.java needs - * to generate code. + * A class that contains all codegen configuration properties a user would want to manipulate. An + * instance could be created by deserializing a JSON file or being populated from CLI or Maven + * plugin parameters. It also has a convenience method for creating a ClientOptInput class which is + * THE object DefaultGenerator.java needs to generate code. */ public class CodegenConfigurator implements Serializable { public static final Logger LOGGER = LoggerFactory.getLogger(CodegenConfigurator.class); - private static Map nameMigrationMap = new HashMap<>(); + private static Map nameMigrationMap = new HashMap<>(); + static { nameMigrationMap.put("akka-scala", new ScalaAkkaClientCodegen().getName()); nameMigrationMap.put("scala", new ScalaHttpClientCodegen().getName()); @@ -94,14 +119,15 @@ public class CodegenConfigurator implements Serializable { private Map additionalProperties = new HashMap(); private Map importMappings = new HashMap(); private Set languageSpecificPrimitives = new HashSet(); - private Map reservedWordMappings = new HashMap(); + private Map reservedWordMappings = new HashMap(); - private String gitUserId="GIT_USER_ID"; - private String gitRepoId="GIT_REPO_ID"; - private String releaseNote="Minor update"; + private String gitUserId = "GIT_USER_ID"; + private String gitRepoId = "GIT_REPO_ID"; + private String releaseNote = "Minor update"; private String httpUserAgent; - private final Map dynamicProperties = new HashMap(); //the map that holds the JsonAnySetter/JsonAnyGetter values + private final Map dynamicProperties = new HashMap(); + //the map that holds the JsonAnySetter/JsonAnyGetter values public CodegenConfigurator() { this.validateSpec = true; @@ -109,18 +135,21 @@ public class CodegenConfigurator implements Serializable { } // TODO: When setLang is removed, please remove nameMigrationMap and its usage(s). + /** - * Set the "language". This has drifted away from language-only to include framework and hyphenated generator types as well as language. + * Set the "language". This has drifted away from language-only to include framework and + * hyphenated generator types as well as language. *

- * NOTE: This will eventually become language only again. It is deprecated in its current state. + * NOTE: This will eventually become language only again. It is deprecated in its current + * state. *

* - * @deprecated Please use {@link #setGeneratorName(String)}, as generators are no longer identified only by language. We may reuse language in the future. * @param lang The generator name. Previously, language name only. * @return The fluent instance of {@link CodegenConfigurator} + * @deprecated Please use {@link #setGeneratorName(String)}, as generators are no longer + * identified only by language. We may reuse language in the future. */ - @Deprecated - public CodegenConfigurator setLang(String lang) { + @Deprecated public CodegenConfigurator setLang(String lang) { this.setGeneratorName(lang); return this; } @@ -128,8 +157,8 @@ public class CodegenConfigurator implements Serializable { /** * Sets the name of the target generator. * - * The generator's name is used to uniquely identify the generator as a mechanism to lookup the desired implementation - * at runtime. + * The generator's name is used to uniquely identify the generator as a mechanism to lookup the + * desired implementation at runtime. * * @param generatorName The name of the generator. * @return The fluent instance of {@link CodegenConfigurator} @@ -137,7 +166,9 @@ public class CodegenConfigurator implements Serializable { public CodegenConfigurator setGeneratorName(final String generatorName) { if (nameMigrationMap.containsKey(generatorName)) { String newValue = nameMigrationMap.get(generatorName); - LOGGER.warn(String.format(Locale.ROOT, "The name '%s' is a deprecated. Please update to the new name of '%s'.", generatorName, newValue)); + LOGGER.warn(String.format(Locale.ROOT, + "The name '%s' is a deprecated. Please update to the new name of '%s'.", + generatorName, newValue)); this.generatorName = newValue; } else { this.generatorName = generatorName; @@ -255,17 +286,18 @@ public class CodegenConfigurator implements Serializable { /** - * Gets the "language". This has drifted away from language-only to include framework and hyphenated generator types as well as language. + * Gets the "language". This has drifted away from language-only to include framework and + * hyphenated generator types as well as language. *

- * NOTE: This will eventually become language only again. It is deprecated in its current state. + * NOTE: This will eventually become language only again. It is deprecated in its current + * state. *

* - * @deprecated Please use {@link #getGeneratorName()}, as generators are no longer identified only by language. We may reuse language in the future. - * * @return A string which defines the generator. + * @deprecated Please use {@link #getGeneratorName()}, as generators are no longer identified + * only by language. We may reuse language in the future. */ - @Deprecated - public String getLang() { + @Deprecated public String getLang() { return getGeneratorName(); } @@ -282,7 +314,8 @@ public class CodegenConfigurator implements Serializable { // check to see if the folder exists if (!(f.exists() && f.isDirectory())) { - throw new IllegalArgumentException("Template directory " + templateDir + " does not exist."); + throw new IllegalArgumentException( + "Template directory " + templateDir + " does not exist."); } this.templateDir = f.getAbsolutePath(); @@ -417,7 +450,8 @@ public class CodegenConfigurator implements Serializable { return languageSpecificPrimitives; } - public CodegenConfigurator setLanguageSpecificPrimitives(Set languageSpecificPrimitives) { + public CodegenConfigurator setLanguageSpecificPrimitives( + Set languageSpecificPrimitives) { this.languageSpecificPrimitives = languageSpecificPrimitives; return this; } @@ -468,11 +502,11 @@ public class CodegenConfigurator implements Serializable { } public CodegenConfigurator setHttpUserAgent(String httpUserAgent) { - this.httpUserAgent= httpUserAgent; + this.httpUserAgent = httpUserAgent; return this; } - public Map getReservedWordsMappings() { + public Map getReservedWordsMappings() { return reservedWordMappings; } @@ -524,7 +558,8 @@ public class CodegenConfigurator implements Serializable { checkAndSetAdditionalProperty(groupId, CodegenConstants.GROUP_ID); checkAndSetAdditionalProperty(artifactId, CodegenConstants.ARTIFACT_ID); checkAndSetAdditionalProperty(artifactVersion, CodegenConstants.ARTIFACT_VERSION); - checkAndSetAdditionalProperty(templateDir, toAbsolutePathStr(templateDir), CodegenConstants.TEMPLATE_DIR); + checkAndSetAdditionalProperty(templateDir, toAbsolutePathStr(templateDir), + CodegenConstants.TEMPLATE_DIR); checkAndSetAdditionalProperty(modelNamePrefix, CodegenConstants.MODEL_NAME_PREFIX); checkAndSetAdditionalProperty(modelNameSuffix, CodegenConstants.MODEL_NAME_SUFFIX); checkAndSetAdditionalProperty(gitUserId, CodegenConstants.GIT_USER_ID); @@ -540,13 +575,13 @@ public class CodegenConfigurator implements Serializable { config.additionalProperties().putAll(additionalProperties); - ClientOptInput input = new ClientOptInput() - .config(config); + ClientOptInput input = new ClientOptInput().config(config); final List authorizationValues = AuthParser.parse(auth); ParseOptions options = new ParseOptions(); options.setResolve(true); - SwaggerParseResult result = new OpenAPIParser().readLocation(inputSpec, authorizationValues, options); + SwaggerParseResult result = + new OpenAPIParser().readLocation(inputSpec, authorizationValues, options); Set validationMessages = new HashSet<>(result.getMessages()); OpenAPI specification = result.getOpenAPI(); @@ -556,12 +591,15 @@ public class CodegenConfigurator implements Serializable { Set warnings = new HashSet<>(); if (specification != null) { List unusedModels = ModelUtils.getUnusedSchemas(specification); - if (unusedModels != null) unusedModels.forEach(name -> warnings.add("Unused model: " + name)); + if (unusedModels != null) { + unusedModels.forEach(name -> warnings.add("Unused model: " + name)); + } } if (this.isValidateSpec()) { StringBuilder sb = new StringBuilder(); - sb.append("There were issues with the specification. The option can be disabled via validateSpec (Maven/Gradle) or --skip-validate-spec (CLI)."); + sb.append( + "There were issues with the specification. The option can be disabled via validateSpec (Maven/Gradle) or --skip-validate-spec (CLI)."); sb.append(System.lineSeparator()); SpecValidationException ex = new SpecValidationException(sb.toString()); ex.setErrors(validationMessages); @@ -569,38 +607,34 @@ public class CodegenConfigurator implements Serializable { throw ex; } else { StringBuilder sb = new StringBuilder(); - sb.append("There were issues with the specification, but validation has been explicitly disabled."); + sb.append( + "There were issues with the specification, but validation has been explicitly disabled."); sb.append(System.lineSeparator()); sb.append("Errors: ").append(System.lineSeparator()); - validationMessages.forEach(msg -> - sb.append("\t-").append(msg).append(System.lineSeparator()) - ); + validationMessages.forEach( + msg -> sb.append("\t-").append(msg).append(System.lineSeparator())); if (!warnings.isEmpty()) { sb.append("Warnings: ").append(System.lineSeparator()); - warnings.forEach(msg -> - sb.append("\t-").append(msg).append(System.lineSeparator()) - ); + warnings.forEach( + msg -> sb.append("\t-").append(msg).append(System.lineSeparator())); } LOGGER.warn(sb.toString()); } } - input.opts(new ClientOpts()) - .openAPI(specification); + input.opts(new ClientOpts()).openAPI(specification); return input; } - @JsonAnySetter - public CodegenConfigurator addDynamicProperty(String name, Object value) { + @JsonAnySetter public CodegenConfigurator addDynamicProperty(String name, Object value) { dynamicProperties.put(name, value); return this; } - @JsonAnyGetter - public Map getDynamicProperties() { + @JsonAnyGetter public Map getDynamicProperties() { return dynamicProperties; } @@ -609,8 +643,7 @@ public class CodegenConfigurator implements Serializable { String opt = langCliOption.getOpt(); if (dynamicProperties.containsKey(opt)) { codegenConfig.additionalProperties().put(opt, dynamicProperties.get(opt)); - } - else if(systemProperties.containsKey(opt)) { + } else if (systemProperties.containsKey(opt)) { codegenConfig.additionalProperties().put(opt, systemProperties.get(opt)); } } @@ -620,11 +653,11 @@ public class CodegenConfigurator implements Serializable { if (!verbose) { return; } - LOGGER.info("\nVERBOSE MODE: ON. Additional debug options are injected" + - "\n - [debugOpenAPI] prints the OpenAPI specification as interpreted by the codegen" + - "\n - [debugModels] prints models passed to the template engine" + - "\n - [debugOperations] prints operations passed to the template engine" + - "\n - [debugSupportingFiles] prints additional data passed to the template engine"); + LOGGER.info("\nVERBOSE MODE: ON. Additional debug options are injected" + + "\n - [debugOpenAPI] prints the OpenAPI specification as interpreted by the codegen" + + "\n - [debugModels] prints models passed to the template engine" + + "\n - [debugOperations] prints operations passed to the template engine" + + "\n - [debugSupportingFiles] prints additional data passed to the template engine"); GeneratorProperties.setProperty("debugOpenAPI", ""); GeneratorProperties.setProperty("debugModels", ""); @@ -651,7 +684,8 @@ public class CodegenConfigurator implements Serializable { checkAndSetAdditionalProperty(property, property, propertyKey); } - private void checkAndSetAdditionalProperty(String property, String valueToSet, String propertyKey) { + private void checkAndSetAdditionalProperty(String property, String valueToSet, + String propertyKey) { if (isNotEmpty(property)) { additionalProperties.put(propertyKey, valueToSet); } @@ -660,13 +694,20 @@ public class CodegenConfigurator implements Serializable { public static CodegenConfigurator fromFile(String configFile) { if (isNotEmpty(configFile)) { + ObjectMapper mapper; + + if (FilenameUtils.isExtension(configFile, new String[] {"yml", "yaml"})) { + mapper = Yaml.mapper(); + } else { + mapper = Json.mapper(); + } + try { - return Json.mapper().readValue(new File(configFile), CodegenConfigurator.class); - } catch (IOException e) { - LOGGER.error("Unable to deserialize config file: " + configFile, e); + return mapper.readValue(new File(configFile), CodegenConfigurator.class); + } catch (IOException ex) { + LOGGER.error("Unable to deserialize config file: " + configFile, ex); } } return null; } - }