[core][gradle] User-defined server variable substitutions (#3363)

* [core] Initial support for server variable overrides
* [gradle] Support user overrides for serverVariables
* [core] Clarify server variable overrides, and propagate them to templates in the "servers" array
This commit is contained in:
Jim Schubert
2019-08-11 09:57:36 -04:00
committed by GitHub
parent 07381e7275
commit 06533b977c
29 changed files with 305 additions and 101 deletions

View File

@@ -43,6 +43,8 @@ public interface CodegenConfig {
Map<String, Object> additionalProperties();
Map<String, String> serverVariableOverrides();
Map<String, Object> vendorExtensions();
String testPackage();

View File

@@ -7,4 +7,5 @@ public class CodegenServerVariable {
public String defaultValue;
public String description;
public List<String> enumValues;
public String value;
}

View File

@@ -97,6 +97,7 @@ public class DefaultCodegen implements CodegenConfig {
protected String embeddedTemplateDir;
protected String commonTemplateDir = "_common";
protected Map<String, Object> additionalProperties = new HashMap<String, Object>();
protected Map<String, String> serverVariables = new HashMap<String, String>();
protected Map<String, Object> vendorExtensions = new HashMap<String, Object>();
protected List<SupportingFile> supportingFiles = new ArrayList<SupportingFile>();
protected List<CliOption> cliOptions = new ArrayList<CliOption>();
@@ -503,12 +504,12 @@ public class DefaultCodegen implements CodegenConfig {
public void postProcessParameter(CodegenParameter parameter) {
}
//override with any special handling of the entire swagger spec
//override with any special handling of the entire OpenAPI spec document
@SuppressWarnings("unused")
public void preprocessOpenAPI(OpenAPI openAPI) {
}
// override with any special handling of the entire swagger spec
// override with any special handling of the entire OpenAPI spec document
@SuppressWarnings("unused")
public void processOpenAPI(OpenAPI openAPI) {
}
@@ -722,6 +723,10 @@ public class DefaultCodegen implements CodegenConfig {
return additionalProperties;
}
public Map<String, String> serverVariableOverrides() {
return serverVariables;
}
public Map<String, Object> vendorExtensions() {
return vendorExtensions;
}
@@ -5053,14 +5058,34 @@ public class DefaultCodegen implements CodegenConfig {
if (variables == null) {
return Collections.emptyList();
}
Map<String, String> variableOverrides = serverVariableOverrides();
List<CodegenServerVariable> codegenServerVariables = new LinkedList<>();
for (Entry<String, ServerVariable> variableEntry : variables.entrySet()) {
CodegenServerVariable codegenServerVariable = new CodegenServerVariable();
ServerVariable variable = variableEntry.getValue();
List<String> enums = variable.getEnum();
codegenServerVariable.defaultValue = variable.getDefault();
codegenServerVariable.description = escapeText(variable.getDescription());
codegenServerVariable.enumValues = variable.getEnum();
codegenServerVariable.enumValues = enums;
codegenServerVariable.name = variableEntry.getKey();
// Sets the override value for a server variable pattern.
// NOTE: OpenAPI Specification doesn't prevent multiple server URLs with variables. If multiple objects have the same
// variables pattern, user overrides will apply to _all_ of these patterns. We may want to consider indexed overrides.
if (variableOverrides != null && !variableOverrides.isEmpty()) {
String value = variableOverrides.getOrDefault(variableEntry.getKey(), variable.getDefault());
codegenServerVariable.value = value;
if (enums != null && !enums.isEmpty() && !enums.contains(value)) {
LOGGER.warn("Variable override of '{}' is not listed in the enum of allowed values ({}).", value, StringUtils.join(enums, ","));
}
} else {
codegenServerVariable.value = variable.getDefault();
}
codegenServerVariables.add(codegenServerVariable);
}
return codegenServerVariables;

View File

@@ -207,10 +207,12 @@ public class DefaultGenerator extends AbstractGenerator implements Generator {
config.vendorExtensions().putAll(openAPI.getExtensions());
}
URL url = URLPathUtils.getServerURL(openAPI);
// TODO: Allow user to define _which_ servers object in the array to target.
// Configures contextPath/basePath according to api document's servers
URL url = URLPathUtils.getServerURL(openAPI, config.serverVariableOverrides());
contextPath = config.escapeText(url.getPath()).replaceAll("/$", ""); // for backward compatibility
basePathWithoutHost = contextPath;
basePath = config.escapeText(URLPathUtils.getHost(openAPI)).replaceAll("/$", "");
basePath = config.escapeText(URLPathUtils.getHost(openAPI, config.serverVariableOverrides())).replaceAll("/$", "");
}
private void configureOpenAPIInfo() {
@@ -548,7 +550,7 @@ public class DefaultGenerator extends AbstractGenerator implements Generator {
}
});
Map<String, Object> operation = processOperations(config, tag, ops, allModels);
URL url = URLPathUtils.getServerURL(openAPI);
URL url = URLPathUtils.getServerURL(openAPI, config.serverVariableOverrides());
operation.put("basePath", basePath);
operation.put("basePathWithoutHost", config.encodePath(url.getPath()).replaceAll("/$", ""));
operation.put("contextPath", contextPath);
@@ -819,7 +821,7 @@ public class DefaultGenerator extends AbstractGenerator implements Generator {
Map<String, Object> apis = new HashMap<String, Object>();
apis.put("apis", allOperations);
URL url = URLPathUtils.getServerURL(openAPI);
URL url = URLPathUtils.getServerURL(openAPI, config.serverVariableOverrides());
bundle.put("openAPI", openAPI);
bundle.put("basePath", basePath);

View File

@@ -67,6 +67,7 @@ public class CodegenConfigurator {
private Map<String, String> importMappings = new HashMap<>();
private Set<String> languageSpecificPrimitives = new HashSet<>();
private Map<String, String> reservedWordMappings = new HashMap<>();
private Map<String, String> serverVariables = new HashMap<>();
private String auth;
public CodegenConfigurator() {
@@ -98,6 +99,12 @@ public class CodegenConfigurator {
return null;
}
public CodegenConfigurator addServerVariable(String key, String value) {
this.serverVariables.put(key, value);
generatorSettingsBuilder.withServerVariable(key, value);
return this;
}
public CodegenConfigurator addAdditionalProperty(String key, Object value) {
this.additionalProperties.put(key, value);
generatorSettingsBuilder.withAdditionalProperty(key, value);
@@ -146,6 +153,18 @@ public class CodegenConfigurator {
return this;
}
public CodegenConfigurator setServerVariables(Map<String, String> serverVariables) {
this.serverVariables = serverVariables;
generatorSettingsBuilder.withServerVariables(serverVariables);
return this;
}
public CodegenConfigurator setReservedWordsMappings(Map<String, String> reservedWordMappings) {
this.reservedWordMappings = reservedWordMappings;
generatorSettingsBuilder.withReservedWordMappings(reservedWordMappings);
return this;
}
public CodegenConfigurator setApiPackage(String apiPackage) {
generatorSettingsBuilder.withApiPackage(apiPackage);
return this;
@@ -247,6 +266,7 @@ public class CodegenConfigurator {
public CodegenConfigurator setLanguageSpecificPrimitives(
Set<String> languageSpecificPrimitives) {
this.languageSpecificPrimitives = languageSpecificPrimitives;
generatorSettingsBuilder.withLanguageSpecificPrimitives(languageSpecificPrimitives);
return this;
}
@@ -296,12 +316,6 @@ public class CodegenConfigurator {
return this;
}
public CodegenConfigurator setReservedWordsMappings(Map<String, String> reservedWordMappings) {
this.reservedWordMappings = reservedWordMappings;
generatorSettingsBuilder.withReservedWordMappings(reservedWordMappings);
return this;
}
public CodegenConfigurator setSkipOverwrite(boolean skipOverwrite) {
workflowSettingsBuilder.withSkipOverwrite(skipOverwrite);
return this;
@@ -459,6 +473,13 @@ public class CodegenConfigurator {
config.reservedWordsMappings().putAll(generatorSettings.getReservedWordMappings());
config.additionalProperties().putAll(generatorSettings.getAdditionalProperties());
Map<String, String> serverVariables = generatorSettings.getServerVariables();
if (!serverVariables.isEmpty()) {
// This is currently experimental due to vagueness in the specification
LOGGER.warn("user-defined server variable support is experimental.");
config.serverVariableOverrides().putAll(serverVariables);
}
// any other additional properties?
String templateDir = workflowSettings.getTemplateDir();
if (templateDir != null) {

View File

@@ -107,6 +107,19 @@ public final class CodegenConfiguratorUtils {
}
}
public static void applyServerVariablesKvpList(List<String> values, CodegenConfigurator configurator) {
for(String value : values) {
applyServerVariablesKvp(value, configurator);
}
}
public static void applyServerVariablesKvp(String values, CodegenConfigurator configurator) {
final Map<String, String> map = createMapFromKeyValuePairs(values);
for (Map.Entry<String, String> entry : map.entrySet()) {
configurator.addServerVariable(entry.getKey(), entry.getValue());
}
}
public static void applyLanguageSpecificPrimitivesCsvList(List<String> languageSpecificPrimitives, CodegenConfigurator configurator) {
for(String propString : languageSpecificPrimitives) {
applyLanguageSpecificPrimitivesCsv(propString, configurator);

View File

@@ -296,7 +296,7 @@ abstract public class AbstractCppCodegen extends DefaultCodegen implements Codeg
@Override
public void preprocessOpenAPI(OpenAPI openAPI) {
URL url = URLPathUtils.getServerURL(openAPI);
URL url = URLPathUtils.getServerURL(openAPI, serverVariableOverrides());
String port = URLPathUtils.getPort(url, "");
String host = url.getHost();
if(!port.isEmpty()) {

View File

@@ -110,7 +110,7 @@ public abstract class AbstractJavaJAXRSServerCodegen extends AbstractJavaCodegen
*/
if (!this.additionalProperties.containsKey(SERVER_PORT)) {
URL url = URLPathUtils.getServerURL(openAPI);
URL url = URLPathUtils.getServerURL(openAPI, serverVariableOverrides());
// 8080 is the default value for a JEE Server:
this.additionalProperties.put(SERVER_PORT, URLPathUtils.getPort(url, serverPort));
}

View File

@@ -268,7 +268,7 @@ public class AspNetCoreServerCodegen extends AbstractCSharpCodegen {
@Override
public void preprocessOpenAPI(OpenAPI openAPI) {
super.preprocessOpenAPI(openAPI);
URL url = URLPathUtils.getServerURL(openAPI);
URL url = URLPathUtils.getServerURL(openAPI, serverVariableOverrides());
additionalProperties.put("serverHost", url.getHost());
additionalProperties.put("serverPort", URLPathUtils.getPort(url, 8080));
}

View File

@@ -359,7 +359,7 @@ public class CSharpNancyFXServerCodegen extends AbstractCSharpCodegen {
@Override
public void preprocessOpenAPI(final OpenAPI openAPI) {
URL url = URLPathUtils.getServerURL(openAPI);
URL url = URLPathUtils.getServerURL(openAPI, serverVariableOverrides());
String path = URLPathUtils.getPath(url, "/");
final String packageContextOption = (String) additionalProperties.get(PACKAGE_CONTEXT);
additionalProperties.put("packageContext", packageContextOption == null ? sanitizeName(path) : packageContextOption);

View File

@@ -23,20 +23,14 @@ import org.openapitools.codegen.CodegenConstants;
import org.openapitools.codegen.CodegenOperation;
import org.openapitools.codegen.CodegenType;
import org.openapitools.codegen.SupportingFile;
import org.openapitools.codegen.CodegenModel;
import org.openapitools.codegen.CodegenProperty;
import org.openapitools.codegen.utils.URLPathUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.net.URL;
import java.util.Arrays;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.List;
import java.util.ArrayList;
import static java.util.UUID.randomUUID;
@@ -165,7 +159,7 @@ public class FsharpGiraffeServerCodegen extends AbstractFSharpCodegen {
@Override
public void preprocessOpenAPI(OpenAPI openAPI) {
super.preprocessOpenAPI(openAPI);
URL url = URLPathUtils.getServerURL(openAPI);
URL url = URLPathUtils.getServerURL(openAPI, serverVariableOverrides());
additionalProperties.put("serverHost", url.getHost());
additionalProperties.put("serverPort", URLPathUtils.getPort(url, 8080));
}

View File

@@ -538,7 +538,7 @@ public class JavaPKMSTServerCodegen extends AbstractJavaCodegen {
additionalProperties.put(TITLE, this.title);
}
URL url = URLPathUtils.getServerURL(openAPI);
URL url = URLPathUtils.getServerURL(openAPI, serverVariableOverrides());
this.additionalProperties.put("serverPort", URLPathUtils.getPort(url, 8080));
if (openAPI.getPaths() != null) {

View File

@@ -221,7 +221,7 @@ public class JavaVertXServerCodegen extends AbstractJavaCodegen {
super.preprocessOpenAPI(openAPI);
// add server port from the swagger file, 8080 by default
URL url = URLPathUtils.getServerURL(openAPI);
URL url = URLPathUtils.getServerURL(openAPI, serverVariableOverrides());
this.additionalProperties.put("serverPort", URLPathUtils.getPort(url, 8080));
// retrieve api version from swagger file, 1.0.0-SNAPSHOT by default

View File

@@ -407,7 +407,7 @@ public class KotlinSpringServerCodegen extends AbstractKotlinCodegen
}
if (!additionalProperties.containsKey(SERVER_PORT)) {
URL url = URLPathUtils.getServerURL(openAPI);
URL url = URLPathUtils.getServerURL(openAPI, serverVariableOverrides());
this.additionalProperties.put(SERVER_PORT, URLPathUtils.getPort(url, 8080));
}

View File

@@ -300,7 +300,7 @@ public class NodeJSExpressServerCodegen extends DefaultCodegen implements Codege
@Override
public void preprocessOpenAPI(OpenAPI openAPI) {
URL url = URLPathUtils.getServerURL(openAPI);
URL url = URLPathUtils.getServerURL(openAPI, serverVariableOverrides());
String host = URLPathUtils.getProtocolAndHost(url);
String port = URLPathUtils.getPort(url, defaultServerPort) ;
String basePath = url.getPath();

View File

@@ -358,7 +358,7 @@ public class NodeJSServerCodegen extends DefaultCodegen implements CodegenConfig
@Override
public void preprocessOpenAPI(OpenAPI openAPI) {
URL url = URLPathUtils.getServerURL(openAPI);
URL url = URLPathUtils.getServerURL(openAPI, serverVariableOverrides());
String host = URLPathUtils.getProtocolAndHost(url);
String port = URLPathUtils.getPort(url, defaultServerPort) ;
String basePath = url.getPath();

View File

@@ -24,9 +24,7 @@ import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.media.ArraySchema;
import io.swagger.v3.oas.models.media.FileSchema;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.media.StringSchema;
import io.swagger.v3.oas.models.media.XML;
import io.swagger.v3.oas.models.parameters.Parameter;
import io.swagger.v3.oas.models.parameters.RequestBody;
import io.swagger.v3.oas.models.servers.Server;
import org.apache.commons.lang3.StringUtils;
@@ -291,7 +289,7 @@ public class RustServerCodegen extends DefaultCodegen implements CodegenConfig {
}
info.setVersion(StringUtils.join(versionComponents, "."));
URL url = URLPathUtils.getServerURL(openAPI);
URL url = URLPathUtils.getServerURL(openAPI, serverVariableOverrides());
additionalProperties.put("serverHost", url.getHost());
additionalProperties.put("serverPort", URLPathUtils.getPort(url, 80));
}

View File

@@ -515,8 +515,8 @@ public class SpringCodegen extends AbstractJavaCodegen
additionalProperties.put(TITLE, this.title);
}
if (!additionalProperties.containsKey(SERVER_PORT)) {
URL url = URLPathUtils.getServerURL(openAPI);
if(!additionalProperties.containsKey(SERVER_PORT)) {
URL url = URLPathUtils.getServerURL(openAPI, serverVariableOverrides());
this.additionalProperties.put(SERVER_PORT, URLPathUtils.getPort(url, 8080));
}

View File

@@ -17,6 +17,7 @@
package org.openapitools.codegen.utils;
import com.google.common.collect.ImmutableMap;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.servers.Server;
import io.swagger.v3.oas.models.servers.ServerVariable;
@@ -28,9 +29,7 @@ import org.slf4j.LoggerFactory;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -40,24 +39,28 @@ public class URLPathUtils {
public static final String LOCAL_HOST = "http://localhost";
public static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{([^\\}]+)\\}");
public static URL getServerURL(OpenAPI openAPI) {
// TODO: This should probably be moved into generator/workflow type rather than a static like this.
public static URL getServerURL(OpenAPI openAPI, Map<String, String> userDefinedVariables) {
final List<Server> servers = openAPI.getServers();
if (servers == null || servers.isEmpty()) {
LOGGER.warn("Server information seems not defined in the spec. Default to {}.", LOCAL_HOST);
return getDefaultUrl();
}
// TODO need a way to obtain all server URLs
return getServerURL(servers.get(0));
return getServerURL(servers.get(0), userDefinedVariables);
}
public static URL getServerURL(final Server server) {
public static URL getServerURL(final Server server, final Map<String, String> userDefinedVariables) {
String url = server.getUrl();
ServerVariables variables = server.getVariables();
if (variables == null) {
variables = new ServerVariables();
}
Map<String, String> userVariables = userDefinedVariables == null ? new HashMap<>() : ImmutableMap.copyOf(userDefinedVariables);
if (StringUtils.isNotBlank(url)) {
url = extractUrl(server, url, variables);
url = extractUrl(server, url, variables, userVariables);
url = sanitizeUrl(url);
try {
@@ -69,26 +72,37 @@ public class URLPathUtils {
return getDefaultUrl();
}
private static String extractUrl(Server server, String url, ServerVariables variables) {
private static String extractUrl(Server server, String url, ServerVariables variables, Map<String, String> userVariables) {
Set<String> replacedVariables = new HashSet<>();
Matcher matcher = VARIABLE_PATTERN.matcher(url);
while (matcher.find()) {
if (!replacedVariables.contains(matcher.group())) {
ServerVariable variable = variables.get(matcher.group(1));
String variableName = matcher.group(1);
ServerVariable variable = variables.get(variableName);
String replacement;
if (variable != null) {
if (variable.getDefault() != null) {
replacement = variable.getDefault();
} else if (variable.getEnum() != null && !variable.getEnum().isEmpty()) {
replacement = variable.getEnum().get(0);
} else {
LOGGER.warn("No value found for variable '{}' in server definition '{}', default to empty string.", matcher.group(1), server.getUrl());
replacement = "";
String defaultValue = variable.getDefault();
List<String> enumValues = variable.getEnum() == null ? new ArrayList<>() : variable.getEnum();
if (defaultValue == null && !enumValues.isEmpty()) {
defaultValue = enumValues.get(0);
} else if (defaultValue == null) {
defaultValue = "";
}
replacement = userVariables.getOrDefault(variableName, defaultValue);
if (!enumValues.isEmpty() && !enumValues.contains(replacement)) {
LOGGER.warn("Variable override of '{}' is not listed in the enum of allowed values ({}).", replacement, StringUtils.join(enumValues, ","));
}
} else {
LOGGER.warn("No variable '{}' found in server definition '{}', default to empty string.", matcher.group(1), server.getUrl());
replacement = "";
replacement = userVariables.getOrDefault(variableName, "");
}
if (StringUtils.isEmpty(replacement)) {
replacement = "";
LOGGER.warn("No value found for variable '{}' in server definition '{}' and no user override specified, default to empty string.", variableName, server.getUrl());
}
url = url.replace(matcher.group(), replacement);
replacedVariables.add(matcher.group());
matcher = VARIABLE_PATTERN.matcher(url);
@@ -98,7 +112,7 @@ public class URLPathUtils {
}
public static String getScheme(OpenAPI openAPI, CodegenConfig config) {
URL url = getServerURL(openAPI);
URL url = getServerURL(openAPI, config.serverVariableOverrides());
return getScheme(url, config);
}
@@ -176,11 +190,12 @@ public class URLPathUtils {
* Return the first complete URL from the OpenAPI specification
*
* @param openAPI current OpenAPI specification
* @param userDefinedVariables User overrides for server variable templating
* @return host
*/
public static String getHost(OpenAPI openAPI) {
public static String getHost(OpenAPI openAPI, final Map<String, String> userDefinedVariables) {
if (openAPI.getServers() != null && openAPI.getServers().size() > 0) {
return sanitizeUrl(getServerURL(openAPI.getServers().get(0)).toString());
return sanitizeUrl(getServerURL(openAPI.getServers().get(0), userDefinedVariables).toString());
}
return LOCAL_HOST;
}