[#2425] - implemented yaml parsing for config file. (#2434)

This commit is contained in:
dragosnutu 2019-03-18 15:49:45 +02:00 committed by William Cheng
parent 0b15fac3e1
commit 1f45ea7d1a
5 changed files with 181 additions and 83 deletions

View File

@ -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.

View File

@ -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
```

View File

@ -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;

View File

@ -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 {

View File

@ -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<String,String> nameMigrationMap = new HashMap<>();
private static Map<String, String> 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<String, Object> additionalProperties = new HashMap<String, Object>();
private Map<String, String> importMappings = new HashMap<String, String>();
private Set<String> languageSpecificPrimitives = new HashSet<String>();
private Map<String, String> reservedWordMappings = new HashMap<String, String>();
private Map<String, String> reservedWordMappings = new HashMap<String, String>();
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<String, Object> dynamicProperties = new HashMap<String, Object>(); //the map that holds the JsonAnySetter/JsonAnyGetter values
private final Map<String, Object> dynamicProperties = new HashMap<String, Object>();
//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.
* <p>
* 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.
* </p>
*
* @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.
* <p>
* 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.
* </p>
*
* @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<String> languageSpecificPrimitives) {
public CodegenConfigurator setLanguageSpecificPrimitives(
Set<String> 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<String, String> getReservedWordsMappings() {
public Map<String, String> 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<AuthorizationValue> 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<String> validationMessages = new HashSet<>(result.getMessages());
OpenAPI specification = result.getOpenAPI();
@ -556,12 +591,15 @@ public class CodegenConfigurator implements Serializable {
Set<String> warnings = new HashSet<>();
if (specification != null) {
List<String> 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<String, Object> getDynamicProperties() {
@JsonAnyGetter public Map<String, Object> 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;
}
}