Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.openapitools.codegen.DefaultCodegen Maven / Gradle / Ivy
/*
* Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech)
* Copyright 2018 SmartBear Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openapitools.codegen;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.Ticker;
import com.google.common.base.CaseFormat;
import com.google.common.collect.ImmutableMap;
import com.samskivert.mustache.Mustache;
import com.samskivert.mustache.Mustache.Compiler;
import com.samskivert.mustache.Mustache.Lambda;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.openapitools.codegen.CodegenDiscriminator.MappedModel;
import org.openapitools.codegen.api.TemplatingEngineAdapter;
import org.openapitools.codegen.config.GlobalSettings;
import org.openapitools.codegen.examples.ExampleGenerator;
import org.openapitools.codegen.meta.FeatureSet;
import org.openapitools.codegen.meta.GeneratorMetadata;
import org.openapitools.codegen.meta.Stability;
import org.openapitools.codegen.meta.features.*;
import org.openapitools.codegen.serializer.SerializerUtils;
import org.openapitools.codegen.templating.MustacheEngineAdapter;
import org.openapitools.codegen.templating.mustache.*;
import org.openapitools.codegen.utils.ModelUtils;
import org.openapitools.codegen.utils.OneOfImplementorAdditionalData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import io.swagger.v3.core.util.Json;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.PathItem;
import io.swagger.v3.oas.models.callbacks.Callback;
import io.swagger.v3.oas.models.examples.Example;
import io.swagger.v3.oas.models.headers.Header;
import io.swagger.v3.oas.models.media.*;
import io.swagger.v3.oas.models.parameters.*;
import io.swagger.v3.oas.models.responses.ApiResponse;
import io.swagger.v3.oas.models.responses.ApiResponses;
import io.swagger.v3.oas.models.security.OAuthFlow;
import io.swagger.v3.oas.models.security.OAuthFlows;
import io.swagger.v3.oas.models.security.SecurityScheme;
import io.swagger.v3.oas.models.servers.Server;
import io.swagger.v3.oas.models.servers.ServerVariable;
import io.swagger.v3.parser.util.SchemaTypeUtil;
import static org.openapitools.codegen.utils.OnceLogger.once;
import static org.openapitools.codegen.utils.StringUtils.*;
public class DefaultCodegen implements CodegenConfig {
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultCodegen.class);
public static FeatureSet DefaultFeatureSet;
// A cache of sanitized words. The sanitizeName() method is invoked many times with the same
// arguments, this cache is used to optimized performance.
private static Cache sanitizedNameCache;
static {
DefaultFeatureSet = FeatureSet.newBuilder()
.includeDataTypeFeatures(
DataTypeFeature.Int32, DataTypeFeature.Int64, DataTypeFeature.Float, DataTypeFeature.Double,
DataTypeFeature.Decimal, DataTypeFeature.String, DataTypeFeature.Byte, DataTypeFeature.Binary,
DataTypeFeature.Boolean, DataTypeFeature.Date, DataTypeFeature.DateTime, DataTypeFeature.Password,
DataTypeFeature.File, DataTypeFeature.Array, DataTypeFeature.Maps, DataTypeFeature.CollectionFormat,
DataTypeFeature.CollectionFormatMulti, DataTypeFeature.Enum, DataTypeFeature.ArrayOfEnum, DataTypeFeature.ArrayOfModel,
DataTypeFeature.ArrayOfCollectionOfPrimitives, DataTypeFeature.ArrayOfCollectionOfModel, DataTypeFeature.ArrayOfCollectionOfEnum,
DataTypeFeature.MapOfEnum, DataTypeFeature.MapOfModel, DataTypeFeature.MapOfCollectionOfPrimitives,
DataTypeFeature.MapOfCollectionOfModel, DataTypeFeature.MapOfCollectionOfEnum
// Custom types are template specific
)
.includeDocumentationFeatures(
DocumentationFeature.Api, DocumentationFeature.Model
// README is template specific
)
.includeGlobalFeatures(
GlobalFeature.Host, GlobalFeature.BasePath, GlobalFeature.Info, GlobalFeature.PartialSchemes,
GlobalFeature.Consumes, GlobalFeature.Produces, GlobalFeature.ExternalDocumentation, GlobalFeature.Examples,
GlobalFeature.Callbacks
// TODO: xml structures, styles, link objects, parameterized servers, full schemes for OAS 2.0
)
.includeSchemaSupportFeatures(
SchemaSupportFeature.Simple, SchemaSupportFeature.Composite,
SchemaSupportFeature.Polymorphism
// Union (OneOf) not 100% yet.
)
.includeParameterFeatures(
ParameterFeature.Path, ParameterFeature.Query, ParameterFeature.Header, ParameterFeature.Body,
ParameterFeature.FormUnencoded, ParameterFeature.FormMultipart, ParameterFeature.Cookie
)
.includeSecurityFeatures(
SecurityFeature.BasicAuth, SecurityFeature.ApiKey, SecurityFeature.BearerToken,
SecurityFeature.OAuth2_Implicit, SecurityFeature.OAuth2_Password,
SecurityFeature.OAuth2_ClientCredentials, SecurityFeature.OAuth2_AuthorizationCode
// OpenIDConnect not yet supported
)
.includeWireFormatFeatures(
WireFormatFeature.JSON, WireFormatFeature.XML
// PROTOBUF and Custom are generator specific
)
.build();
int cacheSize = Integer.parseInt(GlobalSettings.getProperty(NAME_CACHE_SIZE_PROPERTY, "500"));
int cacheExpiry = Integer.parseInt(GlobalSettings.getProperty(NAME_CACHE_EXPIRY_PROPERTY, "10"));
sanitizedNameCache = Caffeine.newBuilder()
.maximumSize(cacheSize)
.expireAfterAccess(cacheExpiry, TimeUnit.SECONDS)
.ticker(Ticker.systemTicker())
.build();
}
protected GeneratorMetadata generatorMetadata;
protected String inputSpec;
protected String outputFolder = "";
protected Set defaultIncludes = new HashSet();
protected Map typeMapping = new HashMap();
protected Map instantiationTypes = new HashMap();
protected Set reservedWords = new HashSet();
protected Set languageSpecificPrimitives = new HashSet();
protected Map importMapping = new HashMap();
protected String modelPackage = "", apiPackage = "", fileSuffix;
protected String modelNamePrefix = "", modelNameSuffix = "";
protected String apiNamePrefix = "", apiNameSuffix = "Api";
protected String testPackage = "";
/*
apiTemplateFiles are for API outputs only (controllers/handlers).
API templates may be written multiple times; APIs are grouped by tag and the file is written once per tag group.
*/
protected Map apiTemplateFiles = new HashMap();
protected Map modelTemplateFiles = new HashMap();
protected Map apiTestTemplateFiles = new HashMap();
protected Map modelTestTemplateFiles = new HashMap();
protected Map apiDocTemplateFiles = new HashMap();
protected Map modelDocTemplateFiles = new HashMap();
protected Map reservedWordsMappings = new HashMap();
protected String templateDir;
protected String embeddedTemplateDir;
protected Map additionalProperties = new HashMap();
protected Map serverVariables = new HashMap();
protected Map vendorExtensions = new HashMap();
/*
Supporting files are those which aren't models, APIs, or docs.
These get a different map of data bound to the templates. Supporting files are written once.
See also 'apiTemplateFiles'.
*/
protected List supportingFiles = new ArrayList();
protected List cliOptions = new ArrayList();
protected boolean skipOverwrite;
protected boolean removeOperationIdPrefix;
/**
* True if the code generator supports multiple class inheritance.
* This is used to model the parent hierarchy based on the 'allOf' composed schemas.
*/
protected boolean supportsMultipleInheritance;
/**
* True if the code generator supports single class inheritance.
* This is used to model the parent hierarchy based on the 'allOf' composed schemas.
* Note: the single-class inheritance technique has inherent limitations because
* a 'allOf' composed schema may have multiple $ref child schemas, each one
* potentially representing a "parent" in the class inheritance hierarchy.
* Some language generators also use class inheritance to implement the `additionalProperties`
* keyword. For example, the Java code generator may generate 'extends HashMap'.
*/
protected boolean supportsInheritance;
/**
* True if the language generator supports the 'additionalProperties' keyword
* as sibling of a composed (allOf/anyOf/oneOf) schema.
* Note: all language generators should support this to comply with the OAS specification.
*/
protected boolean supportsAdditionalPropertiesWithComposedSchema;
protected boolean supportsMixins;
protected Map supportedLibraries = new LinkedHashMap();
protected String library;
protected Boolean sortParamsByRequiredFlag = true;
protected Boolean sortModelPropertiesByRequiredFlag = false;
protected Boolean ensureUniqueParams = true;
protected Boolean allowUnicodeIdentifiers = false;
protected String gitHost, gitUserId, gitRepoId, releaseNote;
protected String httpUserAgent;
protected Boolean hideGenerationTimestamp = true;
// How to encode special characters like $
// They are translated to words like "Dollar" and prefixed with '
// Then translated back during JSON encoding and decoding
protected Map specialCharReplacements = new HashMap();
// When a model is an alias for a simple type
protected Map typeAliases = null;
protected Boolean prependFormOrBodyParameters = false;
// The extension of the generated documentation files (defaults to markdown .md)
protected String docExtension;
protected String ignoreFilePathOverride;
// flag to indicate whether to use environment variable to post process file
protected boolean enablePostProcessFile = false;
private TemplatingEngineAdapter templatingEngine = new MustacheEngineAdapter();
// flag to indicate whether to use the utils.OneOfImplementorAdditionalData related logic
protected boolean useOneOfInterfaces = false;
// whether or not the oneOf imports machinery should add oneOf interfaces as imports in implementing classes
protected boolean addOneOfInterfaceImports = false;
protected List addOneOfInterfaces = new ArrayList();
// flag to indicate whether to only update files whose contents have changed
protected boolean enableMinimalUpdate = false;
// acts strictly upon a spec, potentially modifying it to have consistent behavior across generators.
protected boolean strictSpecBehavior = true;
// flag to indicate whether enum value prefixes are removed
protected boolean removeEnumValuePrefix = true;
// Support legacy logic for evaluating discriminators
protected boolean legacyDiscriminatorBehavior = true;
// Specify what to do if the 'additionalProperties' keyword is not present in a schema.
// See CodegenConstants.java for more details.
protected boolean disallowAdditionalPropertiesIfNotPresent = true;
// make openapi available to all methods
protected OpenAPI openAPI;
// A cache to efficiently lookup a Schema instance based on the return value of `toModelName()`.
private Map modelNameToSchemaCache;
public List cliOptions() {
return cliOptions;
}
public void processOpts() {
if (additionalProperties.containsKey(CodegenConstants.TEMPLATE_DIR)) {
this.setTemplateDir((String) additionalProperties.get(CodegenConstants.TEMPLATE_DIR));
}
if (additionalProperties.containsKey(CodegenConstants.MODEL_PACKAGE)) {
this.setModelPackage((String) additionalProperties.get(CodegenConstants.MODEL_PACKAGE));
}
if (additionalProperties.containsKey(CodegenConstants.API_PACKAGE)) {
this.setApiPackage((String) additionalProperties.get(CodegenConstants.API_PACKAGE));
}
if (additionalProperties.containsKey(CodegenConstants.HIDE_GENERATION_TIMESTAMP)) {
setHideGenerationTimestamp(convertPropertyToBooleanAndWriteBack(CodegenConstants.HIDE_GENERATION_TIMESTAMP));
} else {
additionalProperties.put(CodegenConstants.HIDE_GENERATION_TIMESTAMP, hideGenerationTimestamp);
}
if (additionalProperties.containsKey(CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG)) {
this.setSortParamsByRequiredFlag(Boolean.valueOf(additionalProperties
.get(CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG).toString()));
}
if (additionalProperties.containsKey(CodegenConstants.SORT_MODEL_PROPERTIES_BY_REQUIRED_FLAG)) {
this.setSortModelPropertiesByRequiredFlag(Boolean.valueOf(additionalProperties
.get(CodegenConstants.SORT_MODEL_PROPERTIES_BY_REQUIRED_FLAG).toString()));
}
if (additionalProperties.containsKey(CodegenConstants.PREPEND_FORM_OR_BODY_PARAMETERS)) {
this.setPrependFormOrBodyParameters(Boolean.valueOf(additionalProperties
.get(CodegenConstants.PREPEND_FORM_OR_BODY_PARAMETERS).toString()));
}
if (additionalProperties.containsKey(CodegenConstants.ENSURE_UNIQUE_PARAMS)) {
this.setEnsureUniqueParams(Boolean.valueOf(additionalProperties
.get(CodegenConstants.ENSURE_UNIQUE_PARAMS).toString()));
}
if (additionalProperties.containsKey(CodegenConstants.ALLOW_UNICODE_IDENTIFIERS)) {
this.setAllowUnicodeIdentifiers(Boolean.valueOf(additionalProperties
.get(CodegenConstants.ALLOW_UNICODE_IDENTIFIERS).toString()));
}
if (additionalProperties.containsKey(CodegenConstants.API_NAME_PREFIX)) {
this.setApiNamePrefix((String) additionalProperties.get(CodegenConstants.API_NAME_PREFIX));
}
if (additionalProperties.containsKey(CodegenConstants.API_NAME_SUFFIX)) {
this.setApiNameSuffix((String) additionalProperties.get(CodegenConstants.API_NAME_SUFFIX));
}
if (additionalProperties.containsKey(CodegenConstants.MODEL_NAME_PREFIX)) {
this.setModelNamePrefix((String) additionalProperties.get(CodegenConstants.MODEL_NAME_PREFIX));
}
if (additionalProperties.containsKey(CodegenConstants.MODEL_NAME_SUFFIX)) {
this.setModelNameSuffix((String) additionalProperties.get(CodegenConstants.MODEL_NAME_SUFFIX));
}
if (additionalProperties.containsKey(CodegenConstants.REMOVE_OPERATION_ID_PREFIX)) {
this.setRemoveOperationIdPrefix(Boolean.valueOf(additionalProperties
.get(CodegenConstants.REMOVE_OPERATION_ID_PREFIX).toString()));
}
if (additionalProperties.containsKey(CodegenConstants.DOCEXTENSION)) {
this.setDocExtension(String.valueOf(additionalProperties
.get(CodegenConstants.DOCEXTENSION).toString()));
}
if (additionalProperties.containsKey(CodegenConstants.ENABLE_POST_PROCESS_FILE)) {
this.setEnablePostProcessFile(Boolean.valueOf(additionalProperties
.get(CodegenConstants.ENABLE_POST_PROCESS_FILE).toString()));
}
if (additionalProperties.containsKey(CodegenConstants.GENERATE_ALIAS_AS_MODEL)) {
ModelUtils.setGenerateAliasAsModel(Boolean.valueOf(additionalProperties
.get(CodegenConstants.GENERATE_ALIAS_AS_MODEL).toString()));
}
if (additionalProperties.containsKey(CodegenConstants.REMOVE_ENUM_VALUE_PREFIX)) {
this.setRemoveEnumValuePrefix(Boolean.valueOf(additionalProperties
.get(CodegenConstants.REMOVE_ENUM_VALUE_PREFIX).toString()));
}
if (additionalProperties.containsKey(CodegenConstants.LEGACY_DISCRIMINATOR_BEHAVIOR)) {
this.setLegacyDiscriminatorBehavior(Boolean.valueOf(additionalProperties
.get(CodegenConstants.LEGACY_DISCRIMINATOR_BEHAVIOR).toString()));
}
if (additionalProperties.containsKey(CodegenConstants.DISALLOW_ADDITIONAL_PROPERTIES_IF_NOT_PRESENT)) {
this.setDisallowAdditionalPropertiesIfNotPresent(Boolean.valueOf(additionalProperties
.get(CodegenConstants.DISALLOW_ADDITIONAL_PROPERTIES_IF_NOT_PRESENT).toString()));
}
}
/***
* Preset map builder with commonly used Mustache lambdas.
*
* To extend the map, override addMustacheLambdas(), call parent method
* first and then add additional lambdas to the returned builder.
*
* If common lambdas are not desired, override addMustacheLambdas() method
* and return empty builder.
*
* @return preinitialized map builder with common lambdas
*/
protected ImmutableMap.Builder addMustacheLambdas() {
return new ImmutableMap.Builder()
.put("lowercase", new LowercaseLambda().generator(this))
.put("uppercase", new UppercaseLambda())
.put("titlecase", new TitlecaseLambda())
.put("camelcase", new CamelCaseLambda(true).generator(this))
.put("pascalcase", new CamelCaseLambda(false).generator(this))
.put("indented", new IndentedLambda())
.put("indented_8", new IndentedLambda(8, " "))
.put("indented_12", new IndentedLambda(12, " "))
.put("indented_16", new IndentedLambda(16, " "));
}
private void registerMustacheLambdas() {
ImmutableMap lambdas = addMustacheLambdas().build();
if (lambdas.size() == 0) {
return;
}
if (additionalProperties.containsKey("lambda")) {
LOGGER.error("A property called 'lambda' already exists in additionalProperties");
throw new RuntimeException("A property called 'lambda' already exists in additionalProperties");
}
additionalProperties.put("lambda", lambdas);
}
// override with any special post-processing for all models
@SuppressWarnings({"static-method", "unchecked"})
public Map postProcessAllModels(Map objs) {
if (this.useOneOfInterfaces) {
// First, add newly created oneOf interfaces
for (CodegenModel cm : addOneOfInterfaces) {
Map modelValue = new HashMap<>(additionalProperties());
modelValue.put("model", cm);
List modelsValue = Arrays.asList(modelValue);
List> importsValue = new ArrayList>();
Map objsValue = new HashMap<>();
objsValue.put("models", modelsValue);
objsValue.put("package", modelPackage());
objsValue.put("imports", importsValue);
objsValue.put("classname", cm.classname);
objsValue.putAll(additionalProperties);
objs.put(cm.name, objsValue);
}
// Gather data from all the models that contain oneOf into OneOfImplementorAdditionalData classes
// (see docstring of that class to find out what information is gathered and why)
Map additionalDataMap = new HashMap();
for (Map.Entry modelsEntry : objs.entrySet()) {
Map modelsAttrs = (Map) modelsEntry.getValue();
List models = (List) modelsAttrs.get("models");
List> modelsImports = (List>) modelsAttrs.getOrDefault("imports", new ArrayList>());
for (Object _mo : models) {
Map mo = (Map) _mo;
CodegenModel cm = (CodegenModel) mo.get("model");
if (cm.oneOf.size() > 0) {
cm.vendorExtensions.put("x-is-one-of-interface", true);
for (String one : cm.oneOf) {
if (!additionalDataMap.containsKey(one)) {
additionalDataMap.put(one, new OneOfImplementorAdditionalData(one));
}
additionalDataMap.get(one).addFromInterfaceModel(cm, modelsImports);
}
// if this is oneOf interface, make sure we include the necessary imports for it
addImportsToOneOfInterface(modelsImports);
}
}
}
// Add all the data from OneOfImplementorAdditionalData classes to the implementing models
for (Map.Entry modelsEntry : objs.entrySet()) {
Map modelsAttrs = (Map) modelsEntry.getValue();
List models = (List) modelsAttrs.get("models");
List> imports = (List>) modelsAttrs.get("imports");
for (Object _implmo : models) {
Map implmo = (Map) _implmo;
CodegenModel implcm = (CodegenModel) implmo.get("model");
String modelName = toModelName(implcm.name);
if (additionalDataMap.containsKey(modelName)) {
additionalDataMap.get(modelName).addToImplementor(this, implcm, imports, addOneOfInterfaceImports);
}
}
}
}
return objs;
}
/**
* Return a map from model name to Schema for efficient lookup.
*
* @return map from model name to Schema.
*/
protected Map getModelNameToSchemaCache() {
if (modelNameToSchemaCache == null) {
// Create a cache to efficiently lookup schema based on model name.
Map m = new HashMap();
ModelUtils.getSchemas(openAPI).forEach((key, schema) -> {
m.put(toModelName(key), schema);
});
modelNameToSchemaCache = Collections.unmodifiableMap(m);
}
return modelNameToSchemaCache;
}
/**
* Index all CodegenModels by model name.
*
* @param objs Map of models
* @return map of all models indexed by names
*/
public Map getAllModels(Map objs) {
Map allModels = new HashMap();
for (Entry entry : objs.entrySet()) {
String modelName = toModelName(entry.getKey());
Map inner = (Map) entry.getValue();
List> models = (List>) inner.get("models");
for (Map mo : models) {
CodegenModel cm = (CodegenModel) mo.get("model");
allModels.put(modelName, cm);
}
}
return allModels;
}
/**
* Loop through all models to update different flags (e.g. isSelfReference), children models, etc
*
* @param objs Map of models
* @return maps of models with various updates
*/
public Map updateAllModels(Map objs) {
Map allModels = getAllModels(objs);
// Fix up all parent and interface CodegenModel references.
for (CodegenModel cm : allModels.values()) {
if (cm.getParent() != null) {
cm.setParentModel(allModels.get(cm.getParent()));
}
if (cm.getInterfaces() != null && !cm.getInterfaces().isEmpty()) {
cm.setInterfaceModels(new ArrayList(cm.getInterfaces().size()));
for (String intf : cm.getInterfaces()) {
CodegenModel intfModel = allModels.get(intf);
if (intfModel != null) {
cm.getInterfaceModels().add(intfModel);
}
}
}
}
// Let parent know about all its children
for (String name : allModels.keySet()) {
CodegenModel cm = allModels.get(name);
CodegenModel parent = allModels.get(cm.getParent());
// if a discriminator exists on the parent, don't add this child to the inheritance hierarchy
// TODO Determine what to do if the parent discriminator name == the grandparent discriminator name
while (parent != null) {
if (parent.getChildren() == null) {
parent.setChildren(new ArrayList());
}
parent.getChildren().add(cm);
parent.hasChildren = true;
Schema parentSchema = this.openAPI.getComponents().getSchemas().get(parent.name);
if (parentSchema.getDiscriminator() == null) {
parent = allModels.get(parent.getParent());
} else {
parent = null;
}
}
}
// loop through properties of each model to detect self-reference
for (Map.Entry entry : objs.entrySet()) {
Map inner = (Map) entry.getValue();
List> models = (List>) inner.get("models");
for (Map mo : models) {
CodegenModel cm = (CodegenModel) mo.get("model");
for (CodegenProperty cp : cm.allVars) {
// detect self import
if (cp.dataType.equalsIgnoreCase(cm.classname) ||
(cp.isContainer && cp.items != null && cp.items.dataType.equalsIgnoreCase(cm.classname))) {
cm.imports.remove(cm.classname); // remove self import
cp.isSelfReference = true;
}
}
}
}
setCircularReferences(allModels);
return objs;
}
public void setCircularReferences(Map models) {
final Map> dependencyMap = models.entrySet().stream()
.collect(Collectors.toMap(Entry::getKey, entry -> getModelDependencies(entry.getValue())));
models.keySet().forEach(name -> setCircularReferencesOnProperties(name, dependencyMap));
}
private List getModelDependencies(CodegenModel model) {
return model.getAllVars().stream()
.map(prop -> {
if (prop.isContainer) {
return prop.items.dataType == null ? null : prop;
}
return prop.dataType == null ? null : prop;
})
.filter(prop -> prop != null)
.collect(Collectors.toList());
}
private void setCircularReferencesOnProperties(final String root,
final Map> dependencyMap) {
dependencyMap.getOrDefault(root, new ArrayList<>()).stream()
.forEach(prop -> {
final List unvisited =
Collections.singletonList(prop.isContainer ? prop.items.dataType : prop.dataType);
prop.isCircularReference = isCircularReference(root,
new HashSet<>(),
new ArrayList<>(unvisited),
dependencyMap);
});
}
private boolean isCircularReference(final String root,
final Set visited,
final List unvisited,
final Map> dependencyMap) {
for (int i = 0; i < unvisited.size(); i++) {
final String next = unvisited.get(i);
if (!visited.contains(next)) {
if (next.equals(root)) {
return true;
}
dependencyMap.getOrDefault(next, new ArrayList<>())
.forEach(prop -> unvisited.add(prop.isContainer ? prop.items.dataType : prop.dataType));
visited.add(next);
}
}
return false;
}
// override with any special post-processing
@SuppressWarnings("static-method")
public Map postProcessModels(Map objs) {
return objs;
}
/**
* post process enum defined in model's properties
*
* @param objs Map of models
* @return maps of models with better enum support
*/
public Map postProcessModelsEnum(Map objs) {
List models = (List) objs.get("models");
for (Object _mo : models) {
Map mo = (Map) _mo;
CodegenModel cm = (CodegenModel) mo.get("model");
// for enum model
if (Boolean.TRUE.equals(cm.isEnum) && cm.allowableValues != null) {
Map allowableValues = cm.allowableValues;
List values = (List) allowableValues.get("values");
List> enumVars = buildEnumVars(values, cm.dataType);
// if "x-enum-varnames" or "x-enum-descriptions" defined, update varnames
updateEnumVarsWithExtensions(enumVars, cm.getVendorExtensions(), cm.dataType);
cm.allowableValues.put("enumVars", enumVars);
}
// update codegen property enum with proper naming convention
// and handling of numbers, special characters
for (CodegenProperty var : cm.vars) {
updateCodegenPropertyEnum(var);
}
for (CodegenProperty var : cm.allVars) {
updateCodegenPropertyEnum(var);
}
for (CodegenProperty var : cm.requiredVars) {
updateCodegenPropertyEnum(var);
}
for (CodegenProperty var : cm.optionalVars) {
updateCodegenPropertyEnum(var);
}
for (CodegenProperty var : cm.parentVars) {
updateCodegenPropertyEnum(var);
}
for (CodegenProperty var : cm.readOnlyVars) {
updateCodegenPropertyEnum(var);
}
for (CodegenProperty var : cm.readWriteVars) {
updateCodegenPropertyEnum(var);
}
}
return objs;
}
/**
* Returns the common prefix of variables for enum naming if
* two or more variables are present
*
* @param vars List of variable names
* @return the common prefix for naming
*/
public String findCommonPrefixOfVars(List vars) {
if (vars.size() > 1) {
try {
String[] listStr = vars.toArray(new String[vars.size()]);
String prefix = StringUtils.getCommonPrefix(listStr);
// exclude trailing characters that should be part of a valid variable
// e.g. ["status-on", "status-off"] => "status-" (not "status-o")
return prefix.replaceAll("[a-zA-Z0-9]+\\z", "");
} catch (ArrayStoreException e) {
// do nothing, just return default value
}
}
return "";
}
/**
* Return the enum default value in the language specified format
*
* @param value enum variable name
* @param datatype data type
* @return the default value for the enum
*/
public String toEnumDefaultValue(String value, String datatype) {
return datatype + "." + value;
}
/**
* Return the enum value in the language specified format
* e.g. status becomes "status"
*
* @param value enum variable name
* @param datatype data type
* @return the sanitized value for enum
*/
public String toEnumValue(String value, String datatype) {
if ("number".equalsIgnoreCase(datatype)) {
return value;
} else {
return "\"" + escapeText(value) + "\"";
}
}
/**
* Return the sanitized variable name for enum
*
* @param value enum variable name
* @param datatype data type
* @return the sanitized variable name for enum
*/
public String toEnumVarName(String value, String datatype) {
if (value.length() == 0) {
return "EMPTY";
}
String var = value.replaceAll("\\W+", "_").toUpperCase(Locale.ROOT);
if (var.matches("\\d.*")) {
return "_" + var;
} else {
return var;
}
}
/**
* Set the OpenAPI document.
* This method is invoked when the input OpenAPI document has been parsed and validated.
*/
@Override
public void setOpenAPI(OpenAPI openAPI) {
this.openAPI = openAPI;
// Set global settings such that helper functions in ModelUtils can lookup the value
// of the CLI option.
ModelUtils.setDisallowAdditionalPropertiesIfNotPresent(getDisallowAdditionalPropertiesIfNotPresent());
}
// override with any message to be shown right before the process finishes
@SuppressWarnings("static-method")
public void postProcess() {
System.out.println("################################################################################");
System.out.println("# Thanks for using OpenAPI Generator. #");
System.out.println("# Please consider donation to help us maintain this project \uD83D\uDE4F #");
System.out.println("# https://opencollective.com/openapi_generator/donate #");
System.out.println("################################################################################");
}
// override with any special post-processing
@SuppressWarnings("static-method")
public Map postProcessOperationsWithModels(Map objs, List allModels) {
return objs;
}
// override with any special post-processing
@SuppressWarnings("static-method")
public Map postProcessSupportingFileData(Map objs) {
return objs;
}
// override to post-process any model properties
@SuppressWarnings("unused")
public void postProcessModelProperty(CodegenModel model, CodegenProperty property) {
}
// override to post-process any parameters
@SuppressWarnings("unused")
public void postProcessParameter(CodegenParameter parameter) {
}
//override with any special handling of the entire OpenAPI spec document
@SuppressWarnings("unused")
public void preprocessOpenAPI(OpenAPI openAPI) {
if (useOneOfInterfaces) {
// we process the openapi schema here to find oneOf schemas and create interface models for them
Map schemas = new HashMap(openAPI.getComponents().getSchemas());
if (schemas == null) {
schemas = new HashMap();
}
Map pathItems = openAPI.getPaths();
// we need to add all request and response bodies to processed schemas
if (pathItems != null) {
for (Map.Entry e : pathItems.entrySet()) {
for (Map.Entry op : e.getValue().readOperationsMap().entrySet()) {
String opId = getOrGenerateOperationId(op.getValue(), e.getKey(), op.getKey().toString());
// process request body
RequestBody b = ModelUtils.getReferencedRequestBody(openAPI, op.getValue().getRequestBody());
Schema requestSchema = null;
if (b != null) {
requestSchema = ModelUtils.getSchemaFromRequestBody(b);
}
if (requestSchema != null) {
schemas.put(opId, requestSchema);
}
// process all response bodies
for (Map.Entry ar : op.getValue().getResponses().entrySet()) {
ApiResponse a = ModelUtils.getReferencedApiResponse(openAPI, ar.getValue());
Schema responseSchema = ModelUtils.getSchemaFromResponse(a);
if (responseSchema != null) {
schemas.put(opId + ar.getKey(), responseSchema);
}
}
}
}
}
// also add all properties of all schemas to be checked for oneOf
Map propertySchemas = new HashMap();
for (Map.Entry e : schemas.entrySet()) {
Schema s = e.getValue();
Map props = s.getProperties();
if (props == null) {
props = new HashMap();
}
for (Map.Entry p : props.entrySet()) {
propertySchemas.put(e.getKey() + "/" + p.getKey(), p.getValue());
}
}
schemas.putAll(propertySchemas);
// go through all gathered schemas and add them as interfaces to be created
for (Map.Entry e : schemas.entrySet()) {
String n = toModelName(e.getKey());
Schema s = e.getValue();
String nOneOf = toModelName(n + "OneOf");
if (ModelUtils.isComposedSchema(s)) {
if (e.getKey().contains("/")) {
// if this is property schema, we also need to generate the oneOf interface model
addOneOfNameExtension((ComposedSchema) s, nOneOf);
addOneOfInterfaceModel((ComposedSchema) s, nOneOf, openAPI);
} else {
// else this is a component schema, so we will just use that as the oneOf interface model
addOneOfNameExtension((ComposedSchema) s, n);
}
} else if (ModelUtils.isArraySchema(s)) {
Schema items = ((ArraySchema) s).getItems();
if (ModelUtils.isComposedSchema(items)) {
addOneOfNameExtension((ComposedSchema) items, nOneOf);
addOneOfInterfaceModel((ComposedSchema) items, nOneOf, openAPI);
}
} else if (ModelUtils.isMapSchema(s)) {
Schema addProps = getAdditionalProperties(s);
if (addProps != null && ModelUtils.isComposedSchema(addProps)) {
addOneOfNameExtension((ComposedSchema) addProps, nOneOf);
addOneOfInterfaceModel((ComposedSchema) addProps, nOneOf, openAPI);
}
}
}
}
}
// override with any special handling of the entire OpenAPI spec document
@SuppressWarnings("unused")
public void processOpenAPI(OpenAPI openAPI) {
}
// override with any special handling of the JMustache compiler
@SuppressWarnings("unused")
public Compiler processCompiler(Compiler compiler) {
return compiler;
}
// override with any special handling for the templating engine
@SuppressWarnings("unused")
public TemplatingEngineAdapter processTemplatingEngine(TemplatingEngineAdapter templatingEngine) {
return templatingEngine;
}
// override with any special text escaping logic
@SuppressWarnings("static-method")
public String escapeText(String input) {
if (input == null) {
return input;
}
// remove \t, \n, \r
// replace \ with \\
// replace " with \"
// outter unescape to retain the original multi-byte characters
// finally escalate characters avoiding code injection
return escapeUnsafeCharacters(
StringEscapeUtils.unescapeJava(
StringEscapeUtils.escapeJava(input)
.replace("\\/", "/"))
.replaceAll("[\\t\\n\\r]", " ")
.replace("\\", "\\\\")
.replace("\"", "\\\""));
}
/**
* Escape characters while allowing new lines
*
* @param input String to be escaped
* @return escaped string
*/
public String escapeTextWhileAllowingNewLines(String input) {
if (input == null) {
return input;
}
// remove \t
// replace \ with \\
// replace " with \"
// outter unescape to retain the original multi-byte characters
// finally escalate characters avoiding code injection
return escapeUnsafeCharacters(
StringEscapeUtils.unescapeJava(
StringEscapeUtils.escapeJava(input)
.replace("\\/", "/"))
.replaceAll("[\\t]", " ")
.replace("\\", "\\\\")
.replace("\"", "\\\""));
}
// override with any special encoding and escaping logic
@SuppressWarnings("static-method")
public String encodePath(String input) {
return escapeText(input);
}
/**
* override with any special text escaping logic to handle unsafe
* characters so as to avoid code injection
*
* @param input String to be cleaned up
* @return string with unsafe characters removed or escaped
*/
public String escapeUnsafeCharacters(String input) {
LOGGER.warn("escapeUnsafeCharacters should be overridden in the code generator with proper logic to escape " +
"unsafe characters");
// doing nothing by default and code generator should implement
// the logic to prevent code injection
// later we'll make this method abstract to make sure
// code generator implements this method
return input;
}
/**
* Escape single and/or double quote to avoid code injection
*
* @param input String to be cleaned up
* @return string with quotation mark removed or escaped
*/
public String escapeQuotationMark(String input) {
LOGGER.warn("escapeQuotationMark should be overridden in the code generator with proper logic to escape " +
"single/double quote");
return input.replace("\"", "\\\"");
}
public Set defaultIncludes() {
return defaultIncludes;
}
public Map typeMapping() {
return typeMapping;
}
public Map instantiationTypes() {
return instantiationTypes;
}
public Set reservedWords() {
return reservedWords;
}
public Set languageSpecificPrimitives() {
return languageSpecificPrimitives;
}
public Map importMapping() {
return importMapping;
}
public String testPackage() {
return testPackage;
}
public String modelPackage() {
return modelPackage;
}
public String apiPackage() {
return apiPackage;
}
public String fileSuffix() {
return fileSuffix;
}
public String templateDir() {
return templateDir;
}
public String embeddedTemplateDir() {
if (embeddedTemplateDir != null) {
return embeddedTemplateDir;
} else {
return templateDir;
}
}
public Map apiDocTemplateFiles() {
return apiDocTemplateFiles;
}
public Map modelDocTemplateFiles() {
return modelDocTemplateFiles;
}
public Map reservedWordsMappings() {
return reservedWordsMappings;
}
public Map apiTestTemplateFiles() {
return apiTestTemplateFiles;
}
public Map modelTestTemplateFiles() {
return modelTestTemplateFiles;
}
public Map apiTemplateFiles() {
return apiTemplateFiles;
}
public Map modelTemplateFiles() {
return modelTemplateFiles;
}
public String apiFileFolder() {
return outputFolder + File.separator + apiPackage().replace('.', File.separatorChar);
}
public String modelFileFolder() {
return outputFolder + File.separator + modelPackage().replace('.', File.separatorChar);
}
public String apiTestFileFolder() {
return outputFolder + File.separator + testPackage().replace('.', File.separatorChar);
}
public String modelTestFileFolder() {
return outputFolder + File.separator + testPackage().replace('.', File.separatorChar);
}
public String apiDocFileFolder() {
return outputFolder;
}
public String modelDocFileFolder() {
return outputFolder;
}
public Map additionalProperties() {
return additionalProperties;
}
public Map serverVariableOverrides() {
return serverVariables;
}
public Map vendorExtensions() {
return vendorExtensions;
}
public List supportingFiles() {
return supportingFiles;
}
public String outputFolder() {
return outputFolder;
}
public void setOutputDir(String dir) {
this.outputFolder = dir;
}
public String getOutputDir() {
return outputFolder();
}
public String getInputSpec() {
return inputSpec;
}
public void setInputSpec(String inputSpec) {
this.inputSpec = inputSpec;
}
public void setTemplateDir(String templateDir) {
this.templateDir = templateDir;
}
public void setModelPackage(String modelPackage) {
this.modelPackage = modelPackage;
}
public String getModelNamePrefix() {
return modelNamePrefix;
}
public void setModelNamePrefix(String modelNamePrefix) {
this.modelNamePrefix = modelNamePrefix;
}
public String getModelNameSuffix() {
return modelNameSuffix;
}
public void setModelNameSuffix(String modelNameSuffix) {
this.modelNameSuffix = modelNameSuffix;
}
public String getApiNameSuffix() {
return apiNameSuffix;
}
public void setApiNameSuffix(String apiNameSuffix) {
this.apiNameSuffix = apiNameSuffix;
}
public String getApiNamePrefix() {
return apiNamePrefix;
}
public void setApiNamePrefix(String apiNamePrefix) {
this.apiNamePrefix = apiNamePrefix;
}
public void setApiPackage(String apiPackage) {
this.apiPackage = apiPackage;
}
public Boolean getSortParamsByRequiredFlag() {
return sortParamsByRequiredFlag;
}
public void setSortParamsByRequiredFlag(Boolean sortParamsByRequiredFlag) {
this.sortParamsByRequiredFlag = sortParamsByRequiredFlag;
}
public Boolean getSortModelPropertiesByRequiredFlag() {
return sortModelPropertiesByRequiredFlag;
}
public void setSortModelPropertiesByRequiredFlag(Boolean sortModelPropertiesByRequiredFlag) {
this.sortModelPropertiesByRequiredFlag = sortModelPropertiesByRequiredFlag;
}
public Boolean getPrependFormOrBodyParameters() {
return prependFormOrBodyParameters;
}
public void setPrependFormOrBodyParameters(Boolean prependFormOrBodyParameters) {
this.prependFormOrBodyParameters = prependFormOrBodyParameters;
}
public Boolean getEnsureUniqueParams() {
return ensureUniqueParams;
}
public void setEnsureUniqueParams(Boolean ensureUniqueParams) {
this.ensureUniqueParams = ensureUniqueParams;
}
public Boolean getLegacyDiscriminatorBehavior() {
return legacyDiscriminatorBehavior;
}
public void setLegacyDiscriminatorBehavior(boolean val) {
this.legacyDiscriminatorBehavior = val;
}
public Boolean getDisallowAdditionalPropertiesIfNotPresent() {
return disallowAdditionalPropertiesIfNotPresent;
}
public void setDisallowAdditionalPropertiesIfNotPresent(boolean val) {
this.disallowAdditionalPropertiesIfNotPresent = val;
}
public Boolean getAllowUnicodeIdentifiers() {
return allowUnicodeIdentifiers;
}
public void setAllowUnicodeIdentifiers(Boolean allowUnicodeIdentifiers) {
this.allowUnicodeIdentifiers = allowUnicodeIdentifiers;
}
public Boolean getUseOneOfInterfaces() {
return useOneOfInterfaces;
}
public void setUseOneOfInterfaces(Boolean useOneOfInterfaces) {
this.useOneOfInterfaces = useOneOfInterfaces;
}
/**
* Return the regular expression/JSON schema pattern (http://json-schema.org/latest/json-schema-validation.html#anchor33)
*
* @param pattern the pattern (regular expression)
* @return properly-escaped pattern
*/
public String toRegularExpression(String pattern) {
return addRegularExpressionDelimiter(escapeText(pattern));
}
/**
* Return the file name of the Api Test
*
* @param name the file name of the Api
* @return the file name of the Api
*/
public String toApiFilename(String name) {
return toApiName(name);
}
/**
* Return the file name of the Api Documentation
*
* @param name the file name of the Api
* @return the file name of the Api
*/
public String toApiDocFilename(String name) {
return toApiName(name);
}
/**
* Return the file name of the Api Test
*
* @param name the file name of the Api
* @return the file name of the Api
*/
public String toApiTestFilename(String name) {
return toApiName(name) + "Test";
}
/**
* Return the variable name in the Api
*
* @param name the varible name of the Api
* @return the snake-cased variable name
*/
public String toApiVarName(String name) {
return lowerCamelCase(name);
}
/**
* Return the capitalized file name of the model
*
* @param name the model name
* @return the file name of the model
*/
public String toModelFilename(String name) {
return camelize(name);
}
/**
* Return the capitalized file name of the model test
*
* @param name the model name
* @return the file name of the model
*/
public String toModelTestFilename(String name) {
return camelize(name) + "Test";
}
/**
* Return the capitalized file name of the model documentation
*
* @param name the model name
* @return the file name of the model
*/
public String toModelDocFilename(String name) {
return camelize(name);
}
/**
* Returns metadata about the generator.
*
* @return A provided {@link GeneratorMetadata} instance
*/
@Override
public GeneratorMetadata getGeneratorMetadata() {
return generatorMetadata;
}
/**
* Return the operation ID (method name)
*
* @param operationId operation ID
* @return the sanitized method name
*/
@SuppressWarnings("static-method")
public String toOperationId(String operationId) {
// throw exception if method name is empty
if (StringUtils.isEmpty(operationId)) {
throw new RuntimeException("Empty method name (operationId) not allowed");
}
return operationId;
}
/**
* Return the variable name by removing invalid characters and proper escaping if
* it's a reserved word.
*
* @param name the variable name
* @return the sanitized variable name
*/
public String toVarName(final String name) {
if (reservedWords.contains(name)) {
return escapeReservedWord(name);
} else if (name.chars().anyMatch(character -> specialCharReplacements.containsKey("" + ((char) character)))) {
return escape(name, specialCharReplacements, null, null);
}
return name;
}
/**
* Return the parameter name by removing invalid characters and proper escaping if
* it's a reserved word.
*
* @param name Codegen property object
* @return the sanitized parameter name
*/
public String toParamName(String name) {
name = removeNonNameElementToCamelCase(name); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'.
if (reservedWords.contains(name)) {
return escapeReservedWord(name);
} else if (name.chars().anyMatch(character -> specialCharReplacements.containsKey("" + ((char) character)))) {
return escape(name, specialCharReplacements, null, null);
}
return name;
}
/**
* Return the parameter name of array of model
*
* @param name name of the array model
* @return the sanitized parameter name
*/
public String toArrayModelParamName(String name) {
return toParamName(name);
}
/**
* Return the Enum name (e.g. StatusEnum given 'status')
*
* @param property Codegen property
* @return the Enum name
*/
@SuppressWarnings("static-method")
public String toEnumName(CodegenProperty property) {
return StringUtils.capitalize(property.name) + "Enum";
}
/**
* Return the escaped name of the reserved word
*
* @param name the name to be escaped
* @return the escaped reserved word
*
* throws Runtime exception as reserved word is not allowed (default behavior)
*/
@SuppressWarnings("static-method")
public String escapeReservedWord(String name) {
throw new RuntimeException("reserved word " + name + " not allowed");
}
/**
* Return the fully-qualified "Model" name for import
*
* @param name the name of the "Model"
* @return the fully-qualified "Model" name for import
*/
public String toModelImport(String name) {
if ("".equals(modelPackage())) {
return name;
} else {
return modelPackage() + "." + name;
}
}
/**
* Returns the same content as [[toModelImport]] with key the fully-qualified Model name and value the initial input.
* In case of union types this method has a key for each separate model and import.
* @param name the name of the "Model"
* @return Map of fully-qualified models.
*/
public Map toModelImportMap(String name){
return Collections.singletonMap(this.toModelImport(name),name);
}
/**
* Return the fully-qualified "Api" name for import
*
* @param name the name of the "Api"
* @return the fully-qualified "Api" name for import
*/
public String toApiImport(String name) {
return apiPackage() + "." + name;
}
/**
* Default constructor.
* This method will map between OAS type and language-specified type, as well as mapping
* between OAS type and the corresponding import statement for the language. This will
* also add some language specified CLI options, if any.
* returns string presentation of the example path (it's a constructor)
*/
public DefaultCodegen() {
CodegenType codegenType = getTag();
if (codegenType == null) {
codegenType = CodegenType.OTHER;
}
generatorMetadata = GeneratorMetadata.newBuilder()
.stability(Stability.STABLE)
.featureSet(DefaultFeatureSet)
.generationMessage(String.format(Locale.ROOT, "OpenAPI Generator: %s (%s)", getName(), codegenType.toValue()))
.build();
defaultIncludes = new HashSet(
Arrays.asList("double",
"int",
"long",
"short",
"char",
"float",
"String",
"boolean",
"Boolean",
"Double",
"Void",
"Integer",
"Long",
"Float")
);
typeMapping = new HashMap();
typeMapping.put("array", "List");
typeMapping.put("set", "Set");
typeMapping.put("map", "Map");
typeMapping.put("List", "List");
typeMapping.put("Set", "Set");
typeMapping.put("boolean", "Boolean");
typeMapping.put("string", "String");
typeMapping.put("int", "Integer");
typeMapping.put("float", "Float");
typeMapping.put("double", "Double");
typeMapping.put("number", "BigDecimal");
typeMapping.put("decimal", "BigDecimal");
typeMapping.put("DateTime", "Date");
typeMapping.put("long", "Long");
typeMapping.put("short", "Short");
typeMapping.put("char", "String");
typeMapping.put("object", "Object");
typeMapping.put("integer", "Integer");
typeMapping.put("ByteArray", "byte[]");
typeMapping.put("binary", "File");
typeMapping.put("file", "File");
typeMapping.put("UUID", "UUID");
typeMapping.put("URI", "URI");
typeMapping.put("AnyType", "oas_any_type_not_mapped");
instantiationTypes = new HashMap();
reservedWords = new HashSet();
cliOptions.add(CliOption.newBoolean(CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG,
CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG_DESC).defaultValue(Boolean.TRUE.toString()));
cliOptions.add(CliOption.newBoolean(CodegenConstants.SORT_MODEL_PROPERTIES_BY_REQUIRED_FLAG,
CodegenConstants.SORT_MODEL_PROPERTIES_BY_REQUIRED_FLAG_DESC).defaultValue(Boolean.TRUE.toString()));
cliOptions.add(CliOption.newBoolean(CodegenConstants.ENSURE_UNIQUE_PARAMS, CodegenConstants
.ENSURE_UNIQUE_PARAMS_DESC).defaultValue(Boolean.TRUE.toString()));
// name formatting options
cliOptions.add(CliOption.newBoolean(CodegenConstants.ALLOW_UNICODE_IDENTIFIERS, CodegenConstants
.ALLOW_UNICODE_IDENTIFIERS_DESC).defaultValue(Boolean.FALSE.toString()));
// option to change the order of form/body parameter
cliOptions.add(CliOption.newBoolean(CodegenConstants.PREPEND_FORM_OR_BODY_PARAMETERS,
CodegenConstants.PREPEND_FORM_OR_BODY_PARAMETERS_DESC).defaultValue(Boolean.FALSE.toString()));
// option to change how we process + set the data in the discriminator mapping
CliOption legacyDiscriminatorBehaviorOpt = CliOption.newBoolean(CodegenConstants.LEGACY_DISCRIMINATOR_BEHAVIOR, CodegenConstants.LEGACY_DISCRIMINATOR_BEHAVIOR_DESC).defaultValue(Boolean.TRUE.toString());
Map legacyDiscriminatorBehaviorOpts = new HashMap<>();
legacyDiscriminatorBehaviorOpts.put("true", "The mapping in the discriminator includes descendent schemas that allOf inherit from self and the discriminator mapping schemas in the OAS document.");
legacyDiscriminatorBehaviorOpts.put("false", "The mapping in the discriminator includes any descendent schemas that allOf inherit from self, any oneOf schemas, any anyOf schemas, any x-discriminator-values, and the discriminator mapping schemas in the OAS document AND Codegen validates that oneOf and anyOf schemas contain the required discriminator and throws an error if the discriminator is missing.");
legacyDiscriminatorBehaviorOpt.setEnum(legacyDiscriminatorBehaviorOpts);
cliOptions.add(legacyDiscriminatorBehaviorOpt);
// option to change how we process + set the data in the 'additionalProperties' keyword.
CliOption disallowAdditionalPropertiesIfNotPresentOpt = CliOption.newBoolean(
CodegenConstants.DISALLOW_ADDITIONAL_PROPERTIES_IF_NOT_PRESENT,
CodegenConstants.DISALLOW_ADDITIONAL_PROPERTIES_IF_NOT_PRESENT_DESC).defaultValue(Boolean.TRUE.toString());
Map disallowAdditionalPropertiesIfNotPresentOpts = new HashMap<>();
disallowAdditionalPropertiesIfNotPresentOpts.put("false",
"The 'additionalProperties' implementation is compliant with the OAS and JSON schema specifications.");
disallowAdditionalPropertiesIfNotPresentOpts.put("true",
"Keep the old (incorrect) behaviour that 'additionalProperties' is set to false by default.");
disallowAdditionalPropertiesIfNotPresentOpt.setEnum(disallowAdditionalPropertiesIfNotPresentOpts);
cliOptions.add(disallowAdditionalPropertiesIfNotPresentOpt);
this.setDisallowAdditionalPropertiesIfNotPresent(true);
// initialize special character mapping
initializeSpecialCharacterMapping();
// Register common Mustache lambdas.
registerMustacheLambdas();
}
/**
* Initialize special character mapping
*/
protected void initializeSpecialCharacterMapping() {
// Initialize special characters
specialCharReplacements.put("$", "Dollar");
specialCharReplacements.put("^", "Caret");
specialCharReplacements.put("|", "Pipe");
specialCharReplacements.put("=", "Equal");
specialCharReplacements.put("*", "Star");
specialCharReplacements.put("-", "Minus");
specialCharReplacements.put("&", "Ampersand");
specialCharReplacements.put("%", "Percent");
specialCharReplacements.put("#", "Hash");
specialCharReplacements.put("@", "At");
specialCharReplacements.put("!", "Exclamation");
specialCharReplacements.put("+", "Plus");
specialCharReplacements.put(":", "Colon");
specialCharReplacements.put(">", "Greater_Than");
specialCharReplacements.put("<", "Less_Than");
specialCharReplacements.put(".", "Period");
specialCharReplacements.put("_", "Underscore");
specialCharReplacements.put("?", "Question_Mark");
specialCharReplacements.put(",", "Comma");
specialCharReplacements.put("'", "Quote");
specialCharReplacements.put("\"", "Double_Quote");
specialCharReplacements.put("/", "Slash");
specialCharReplacements.put("\\", "Back_Slash");
specialCharReplacements.put("(", "Left_Parenthesis");
specialCharReplacements.put(")", "Right_Parenthesis");
specialCharReplacements.put("{", "Left_Curly_Bracket");
specialCharReplacements.put("}", "Right_Curly_Bracket");
specialCharReplacements.put("[", "Left_Square_Bracket");
specialCharReplacements.put("]", "Right_Square_Bracket");
specialCharReplacements.put("~", "Tilde");
specialCharReplacements.put("`", "Backtick");
specialCharReplacements.put("<=", "Less_Than_Or_Equal_To");
specialCharReplacements.put(">=", "Greater_Than_Or_Equal_To");
specialCharReplacements.put("!=", "Not_Equal");
specialCharReplacements.put("~=", "Tilde_Equal");
}
/**
* Return the symbol name of a symbol
*
* @param input Symbol (e.g. $)
* @return Symbol name (e.g. Dollar)
*/
protected String getSymbolName(String input) {
return specialCharReplacements.get(input);
}
/**
* Return the example path
*
* @param path the path of the operation
* @param operation OAS operation object
* @return string presentation of the example path
*/
@SuppressWarnings("static-method")
public String generateExamplePath(String path, Operation operation) {
StringBuilder sb = new StringBuilder();
sb.append(path);
if (operation.getParameters() != null) {
int count = 0;
for (Parameter param : operation.getParameters()) {
if (param instanceof QueryParameter) {
StringBuilder paramPart = new StringBuilder();
QueryParameter qp = (QueryParameter) param;
if (count == 0) {
paramPart.append("?");
} else {
paramPart.append(",");
}
count += 1;
if (!param.getRequired()) {
paramPart.append("[");
}
paramPart.append(param.getName()).append("=");
paramPart.append("{");
// TODO support for multi, tsv?
if (qp.getStyle() != null) {
paramPart.append(param.getName()).append("1");
if (Parameter.StyleEnum.FORM.equals(qp.getStyle())) {
if (qp.getExplode() != null && qp.getExplode()) {
paramPart.append(",");
} else {
paramPart.append("&").append(param.getName()).append("=");
paramPart.append(param.getName()).append("2");
}
} else if (Parameter.StyleEnum.PIPEDELIMITED.equals(qp.getStyle())) {
paramPart.append("|");
} else if (Parameter.StyleEnum.SPACEDELIMITED.equals(qp.getStyle())) {
paramPart.append("%20");
} else {
LOGGER.warn("query parameter '" + param.getName() + "style not support: " + qp.getStyle());
}
} else {
paramPart.append(param.getName());
}
paramPart.append("}");
if (!param.getRequired()) {
paramPart.append("]");
}
sb.append(paramPart.toString());
}
}
}
return sb.toString();
}
/**
* Return the instantiation type of the property, especially for map and array
*
* @param schema property schema
* @return string presentation of the instantiation type of the property
*/
public String toInstantiationType(Schema schema) {
if (ModelUtils.isMapSchema(schema)) {
Schema additionalProperties = getAdditionalProperties(schema);
String inner = getSchemaType(additionalProperties);
return instantiationTypes.get("map") + "";
} else if (ModelUtils.isArraySchema(schema)) {
ArraySchema arraySchema = (ArraySchema) schema;
String inner = getSchemaType(getSchemaItems(arraySchema));
String parentType;
if (ModelUtils.isSet(schema)) {
parentType = "set";
} else {
parentType = "array";
}
return instantiationTypes.get(parentType) + "<" + inner + ">";
} else {
return null;
}
}
/**
* Return the example value of the parameter.
*
* @param codegenParameter Codegen parameter
*/
public void setParameterExampleValue(CodegenParameter codegenParameter) {
// set the example value
// if not specified in x-example, generate a default value
// TODO need to revise how to obtain the example value
if (codegenParameter.vendorExtensions != null && codegenParameter.vendorExtensions.containsKey("x-example")) {
codegenParameter.example = Json.pretty(codegenParameter.vendorExtensions.get("x-example"));
} else if (Boolean.TRUE.equals(codegenParameter.isBoolean)) {
codegenParameter.example = "true";
} else if (Boolean.TRUE.equals(codegenParameter.isLong)) {
codegenParameter.example = "789";
} else if (Boolean.TRUE.equals(codegenParameter.isInteger)) {
codegenParameter.example = "56";
} else if (Boolean.TRUE.equals(codegenParameter.isFloat)) {
codegenParameter.example = "3.4";
} else if (Boolean.TRUE.equals(codegenParameter.isDouble)) {
codegenParameter.example = "1.2";
} else if (Boolean.TRUE.equals(codegenParameter.isNumber)) {
codegenParameter.example = "8.14";
} else if (Boolean.TRUE.equals(codegenParameter.isBinary)) {
codegenParameter.example = "BINARY_DATA_HERE";
} else if (Boolean.TRUE.equals(codegenParameter.isByteArray)) {
codegenParameter.example = "BYTE_ARRAY_DATA_HERE";
} else if (Boolean.TRUE.equals(codegenParameter.isFile)) {
codegenParameter.example = "/path/to/file.txt";
} else if (Boolean.TRUE.equals(codegenParameter.isDate)) {
codegenParameter.example = "2013-10-20";
} else if (Boolean.TRUE.equals(codegenParameter.isDateTime)) {
codegenParameter.example = "2013-10-20T19:20:30+01:00";
} else if (Boolean.TRUE.equals(codegenParameter.isUuid)) {
codegenParameter.example = "38400000-8cf0-11bd-b23e-10b96e4ef00d";
} else if (Boolean.TRUE.equals(codegenParameter.isUri)) {
codegenParameter.example = "https://openapi-generator.tech";
} else if (Boolean.TRUE.equals(codegenParameter.isString)) {
codegenParameter.example = codegenParameter.paramName + "_example";
} else if (Boolean.TRUE.equals(codegenParameter.isFreeFormObject)) {
codegenParameter.example = "Object";
}
}
/**
* Return the example value of the parameter.
*
* @param codegenParameter Codegen parameter
* @param parameter Parameter
*/
public void setParameterExampleValue(CodegenParameter codegenParameter, Parameter parameter) {
if (parameter.getExample() != null) {
codegenParameter.example = parameter.getExample().toString();
return;
}
if (parameter.getExamples() != null && !parameter.getExamples().isEmpty()) {
Example example = parameter.getExamples().values().iterator().next();
if (example.getValue() != null) {
codegenParameter.example = example.getValue().toString();
return;
}
}
Schema schema = parameter.getSchema();
if (schema != null && schema.getExample() != null) {
codegenParameter.example = schema.getExample().toString();
return;
}
setParameterExampleValue(codegenParameter);
}
/**
* Return the example value of the parameter.
*
* @param codegenParameter Codegen parameter
* @param requestBody Request body
*/
public void setParameterExampleValue(CodegenParameter codegenParameter, RequestBody requestBody) {
Content content = requestBody.getContent();
if (content.size() > 1) {
// @see ModelUtils.getSchemaFromContent()
once(LOGGER).warn("Multiple MediaTypes found, using only the first one");
}
MediaType mediaType = content.values().iterator().next();
if (mediaType.getExample() != null) {
codegenParameter.example = mediaType.getExample().toString();
return;
}
if (mediaType.getExamples() != null && !mediaType.getExamples().isEmpty()) {
Example example = mediaType.getExamples().values().iterator().next();
if (example.getValue() != null) {
codegenParameter.example = example.getValue().toString();
return;
}
}
setParameterExampleValue(codegenParameter);
}
/**
* Sets the content type of the parameter based on the encoding specified in the request body.
*
* @param codegenParameter Codegen parameter
* @param mediaType MediaType from the request body
*/
public void setParameterContentType(CodegenParameter codegenParameter, MediaType mediaType) {
if (mediaType != null && mediaType.getEncoding() != null) {
Encoding encoding = mediaType.getEncoding().get(codegenParameter.baseName);
if (encoding != null) {
codegenParameter.contentType = encoding.getContentType();
} else {
LOGGER.debug("encoding not specified for {}", codegenParameter.baseName);
}
}
}
/**
* Return the example value of the property
*
* @param schema Property schema
* @return string presentation of the example value of the property
*/
public String toExampleValue(Schema schema) {
if (schema.getExample() != null) {
return schema.getExample().toString();
}
return getPropertyDefaultValue(schema);
}
/**
* Return the default value of the property
*
* Return null if you do NOT want a default value.
* Any non-null value will cause {{#defaultValue} check to pass.
*
* @param schema Property schema
* @return string presentation of the default value of the property
*/
@SuppressWarnings("static-method")
public String toDefaultValue(Schema schema) {
if (schema.getDefault() != null) {
return schema.getDefault().toString();
}
return getPropertyDefaultValue(schema);
}
/**
* Return property value depending on property type.
*
* @param schema property type
* @return property value
*/
@SuppressWarnings("squid:S3923")
private String getPropertyDefaultValue(Schema schema) {
/*
* Although all branches return null, this is left intentionally as examples for new contributors
*/
if (ModelUtils.isBooleanSchema(schema)) {
return "null";
} else if (ModelUtils.isDateSchema(schema)) {
return "null";
} else if (ModelUtils.isDateTimeSchema(schema)) {
return "null";
} else if (ModelUtils.isNumberSchema(schema)) {
return "null";
} else if (ModelUtils.isIntegerSchema(schema)) {
return "null";
} else if (ModelUtils.isStringSchema(schema)) {
return "null";
} else if (ModelUtils.isObjectSchema(schema)) {
return "null";
} else {
return "null";
}
}
/**
* Return the property initialized from a data object
* Useful for initialization with a plain object in Javascript
*
* @param name Name of the property object
* @param schema Property schema
* @return string presentation of the default value of the property
*/
@SuppressWarnings("static-method")
public String toDefaultValueWithParam(String name, Schema schema) {
return " = data." + name + ";";
}
/**
* returns the OpenAPI type for the property. Use getAlias to handle $ref of primitive type
*
* @param schema property schema
* @return string presentation of the type
**/
@SuppressWarnings("static-method")
public String getSchemaType(Schema schema) {
if (schema instanceof ComposedSchema) { // composed schema
ComposedSchema cs = (ComposedSchema) schema;
// Get the interfaces, i.e. the set of elements under 'allOf', 'anyOf' or 'oneOf'.
List schemas = ModelUtils.getInterfaces(cs);
List names = new ArrayList<>();
// Build a list of the schema types under each interface.
// For example, if a 'allOf' composed schema has $ref children,
// add the type of each child to the list of names.
for (Schema s : schemas) {
names.add(getSingleSchemaType(s));
}
if (cs.getAllOf() != null) {
return toAllOfName(names, cs);
} else if (cs.getAnyOf() != null) { // anyOf
return toAnyOfName(names, cs);
} else if (cs.getOneOf() != null) { // oneOf
return toOneOfName(names, cs);
}
}
return getSingleSchemaType(schema);
}
protected Schema> getSchemaItems(ArraySchema schema) {
Schema> items = schema.getItems();
if (items == null) {
LOGGER.error("Undefined array inner type for `{}`. Default to String.", schema.getName());
items = new StringSchema().description("TODO default missing array inner type to string");
schema.setItems(items);
}
return items;
}
protected Schema> getSchemaAdditionalProperties(Schema schema) {
Schema> inner = getAdditionalProperties(schema);
if (inner == null) {
LOGGER.error("`{}` (map property) does not have a proper inner type defined. Default to type:string", schema.getName());
inner = new StringSchema().description("TODO default missing map inner type to string");
schema.setAdditionalProperties(inner);
}
return inner;
}
/**
* Return the name of the 'allOf' composed schema.
*
* @param names List of names
* @param composedSchema composed schema
* @return name of the allOf schema
*/
@SuppressWarnings("static-method")
public String toAllOfName(List names, ComposedSchema composedSchema) {
Map exts = composedSchema.getExtensions();
if (exts != null && exts.containsKey("x-all-of-name")) {
return (String) exts.get("x-all-of-name");
}
if (names.size() == 0) {
LOGGER.error("allOf has no member defined: {}. Default to ERROR_ALLOF_SCHEMA", composedSchema);
return "ERROR_ALLOF_SCHEMA";
} else if (names.size() == 1) {
return names.get(0);
} else {
LOGGER.warn("allOf with multiple schemas defined. Using only the first one: {}", names.get(0));
return names.get(0);
}
}
/**
* Return the name of the anyOf schema
*
* @param names List of names
* @param composedSchema composed schema
* @return name of the anyOf schema
*/
@SuppressWarnings("static-method")
public String toAnyOfName(List names, ComposedSchema composedSchema) {
return "anyOf<" + String.join(",", names) + ">";
}
/**
* Return the name of the oneOf schema.
*
* This name is used to set the value of CodegenProperty.openApiType.
*
* If the 'x-one-of-name' extension is specified in the OAS document, return that value.
* Otherwise, a name is constructed by creating a comma-separated list of all the names
* of the oneOf schemas.
*
* @param names List of names
* @param composedSchema composed schema
* @return name of the oneOf schema
*/
@SuppressWarnings("static-method")
public String toOneOfName(List names, ComposedSchema composedSchema) {
Map exts = composedSchema.getExtensions();
if (exts != null && exts.containsKey("x-one-of-name")) {
return (String) exts.get("x-one-of-name");
}
return "oneOf<" + String.join(",", names) + ">";
}
public Schema unaliasSchema(Schema schema, Map usedImportMappings) {
return ModelUtils.unaliasSchema(this.openAPI, schema, usedImportMappings);
}
/**
* Return a string representation of the schema type, resolving aliasing and references if necessary.
*
* @param schema input
* @return the string representation of the schema type.
*/
protected String getSingleSchemaType(Schema schema) {
Schema unaliasSchema = unaliasSchema(schema, importMapping);
if (StringUtils.isNotBlank(unaliasSchema.get$ref())) { // reference to another definition/schema
// get the schema/model name from $ref
String schemaName = ModelUtils.getSimpleRef(unaliasSchema.get$ref());
if (StringUtils.isNotEmpty(schemaName)) {
if (importMapping.containsKey(schemaName)) {
return schemaName;
}
return getAlias(schemaName);
} else {
LOGGER.warn("Error obtaining the datatype from ref:" + unaliasSchema.get$ref() + ". Default to 'object'");
return "object";
}
} else { // primitive type or model
return getAlias(getPrimitiveType(unaliasSchema));
}
}
/**
* Return the OAI type (e.g. integer, long, etc) corresponding to a schema.
* $ref is not taken into account by this method.
*
* If the schema is free-form (i.e. 'type: object' with no properties) or inline
* schema, the returned OAI type is 'object'.
*
* @param schema
* @return type
*/
private String getPrimitiveType(Schema schema) {
if (schema == null) {
throw new RuntimeException("schema cannot be null in getPrimitiveType");
} else if (ModelUtils.isNullType(schema)) {
// The 'null' type is allowed in OAS 3.1 and above. It is not supported by OAS 3.0.x,
// though this tooling supports it.
return "null";
} else if (ModelUtils.isDecimalSchema(schema)) {
// special handle of type: string, format: number
return "decimal";
} else if (ModelUtils.isByteArraySchema(schema)) {
return "ByteArray";
} else if (ModelUtils.isFileSchema(schema)) {
return "file";
} else if (ModelUtils.isBinarySchema(schema)) {
return SchemaTypeUtil.BINARY_FORMAT;
} else if (ModelUtils.isBooleanSchema(schema)) {
return SchemaTypeUtil.BOOLEAN_TYPE;
} else if (ModelUtils.isDateSchema(schema)) {
return SchemaTypeUtil.DATE_FORMAT;
} else if (ModelUtils.isDateTimeSchema(schema)) {
return "DateTime";
} else if (ModelUtils.isNumberSchema(schema)) {
if (schema.getFormat() == null) { // no format defined
return "number";
} else if (ModelUtils.isFloatSchema(schema)) {
return SchemaTypeUtil.FLOAT_FORMAT;
} else if (ModelUtils.isDoubleSchema(schema)) {
return SchemaTypeUtil.DOUBLE_FORMAT;
} else {
LOGGER.warn("Unknown `format` {} detected for type `number`. Defaulting to `number`", schema.getFormat());
return "number";
}
} else if (ModelUtils.isIntegerSchema(schema)) {
if (ModelUtils.isLongSchema(schema)) {
return "long";
} else {
return schema.getType(); // integer
}
} else if (ModelUtils.isMapSchema(schema)) {
return "map";
} else if (ModelUtils.isArraySchema(schema)) {
if (ModelUtils.isSet(schema)) {
return "set";
} else {
return "array";
}
} else if (ModelUtils.isUUIDSchema(schema)) {
return "UUID";
} else if (ModelUtils.isURISchema(schema)) {
return "URI";
} else if (ModelUtils.isStringSchema(schema)) {
if (typeMapping.containsKey(schema.getFormat())) {
// If the format matches a typeMapping (supplied with the --typeMappings flag)
// then treat the format as a primitive type.
// This allows the typeMapping flag to add a new custom type which can then
// be used in the format field.
return schema.getFormat();
}
return "string";
} else if (isFreeFormObject(schema)) {
// Note: the value of a free-form object cannot be an arbitrary type. Per OAS specification,
// it must be a map of string to values.
return "object";
} else if (schema.getProperties() != null && !schema.getProperties().isEmpty()) { // having property implies it's a model
return "object";
} else if (isAnyTypeSchema(schema)) {
return "AnyType";
} else if (StringUtils.isNotEmpty(schema.getType())) {
if (!importMapping.containsKey(schema.getType())) {
LOGGER.warn("Unknown type found in the schema: " + schema.getType());
}
return schema.getType();
}
// The 'type' attribute has not been set in the OAS schema, which means the value
// can be an arbitrary type, e.g. integer, string, object, array, number...
// TODO: we should return a different value to distinguish between free-form object
// and arbitrary type.
return "object";
}
/**
* Return the lowerCamelCase of the string
*
* @param name string to be lowerCamelCased
* @return lowerCamelCase string
*/
@SuppressWarnings("static-method")
public String lowerCamelCase(String name) {
return (name.length() > 0) ? (Character.toLowerCase(name.charAt(0)) + name.substring(1)) : "";
}
/**
* Output the language-specific type declaration of a given OAS name.
*
* @param name name
* @return a string presentation of the type
*/
@SuppressWarnings("static-method")
public String getTypeDeclaration(String name) {
return name;
}
/**
* Output the language-specific type declaration of the property.
*
* @param schema property schema
* @return a string presentation of the property type
*/
public String getTypeDeclaration(Schema schema) {
if (schema == null) {
LOGGER.warn("Null schema found. Default type to `NULL_SCHEMA_ERR`");
return "NULL_SCHEMA_ERR";
}
String oasType = getSchemaType(schema);
if (typeMapping.containsKey(oasType)) {
return typeMapping.get(oasType);
}
return oasType;
}
/**
* Determine the type alias for the given type if it exists. This feature
* was originally developed for Java because the language does not have an aliasing
* mechanism of its own but later extends to handle other languages
*
* @param name The type name.
* @return The alias of the given type, if it exists. If there is no alias
* for this type, then returns the input type name.
*/
public String getAlias(String name) {
if (typeAliases != null && typeAliases.containsKey(name)) {
return typeAliases.get(name);
}
return name;
}
/**
* Output the Getter name for boolean property, e.g. getActive
*
* @param name the name of the property
* @return getter name based on naming convention
*/
public String toBooleanGetter(String name) {
return "get" + getterAndSetterCapitalize(name);
}
/**
* Output the Getter name, e.g. getSize
*
* @param name the name of the property
* @return getter name based on naming convention
*/
public String toGetter(String name) {
return "get" + getterAndSetterCapitalize(name);
}
/**
* Output the Setter name, e.g. setSize
*
* @param name the name of the property
* @return setter name based on naming convention
*/
public String toSetter(String name) {
return "set" + getterAndSetterCapitalize(name);
}
/**
* Output the API (class) name (capitalized) ending with the specified or default suffix
* Return DefaultApi if name is empty
*
* @param name the name of the Api
* @return capitalized Api name
*/
public String toApiName(String name) {
if (name.length() == 0) {
return "DefaultApi";
}
return camelize(apiNamePrefix + "_" + name + "_" + apiNameSuffix);
}
/**
* Converts the OpenAPI schema name to a model name suitable for the current code generator.
* May be overriden for each programming language.
* In case the name belongs to the TypeSystem it won't be renamed.
*
* @param name the name of the model
* @return capitalized model name
*/
public String toModelName(final String name) {
return camelize(modelNamePrefix + "_" + name + "_" + modelNameSuffix);
}
private static class NamedSchema {
private NamedSchema(String name, Schema s) {
this.name = name;
this.schema = s;
}
private String name;
private Schema schema;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
NamedSchema that = (NamedSchema) o;
return Objects.equals(name, that.name) &&
Objects.equals(schema, that.schema);
}
@Override
public int hashCode() {
return Objects.hash(name, schema);
}
}
Map schemaCodegenPropertyCache = new HashMap();
/**
* Convert OAS Model object to Codegen Model object.
*
* @param name the name of the model
* @param schema OAS Model object
* @return Codegen Model object
*/
public CodegenModel fromModel(String name, Schema schema) {
Map allDefinitions = ModelUtils.getSchemas(this.openAPI);
if (typeAliases == null) {
// Only do this once during first call
typeAliases = getAllAliases(allDefinitions);
}
// unalias schema
schema = unaliasSchema(schema, importMapping);
if (schema == null) {
LOGGER.warn("Schema {} not found", name);
return null;
}
CodegenModel m = CodegenModelFactory.newInstance(CodegenModelType.MODEL);
ModelUtils.syncValidationProperties(schema, m);
if (reservedWords.contains(name)) {
m.name = escapeReservedWord(name);
} else {
m.name = name;
}
m.title = escapeText(schema.getTitle());
m.description = escapeText(schema.getDescription());
m.unescapedDescription = schema.getDescription();
m.classname = toModelName(name);
m.classVarName = toVarName(name);
m.classFilename = toModelFilename(name);
m.modelJson = Json.pretty(schema);
m.externalDocumentation = schema.getExternalDocs();
if (schema.getExtensions() != null && !schema.getExtensions().isEmpty()) {
m.getVendorExtensions().putAll(schema.getExtensions());
}
m.isAlias = (typeAliases.containsKey(name)
|| isAliasOfSimpleTypes(schema)); // check if the unaliased schema is an alias of simple OAS types
m.discriminator = createDiscriminator(name, schema, this.openAPI);
if (!this.getLegacyDiscriminatorBehavior()) {
m.addDiscriminatorMappedModelsImports();
}
if (schema.getDeprecated() != null) {
m.isDeprecated = schema.getDeprecated();
}
if (schema.getXml() != null) {
m.xmlPrefix = schema.getXml().getPrefix();
m.xmlNamespace = schema.getXml().getNamespace();
m.xmlName = schema.getXml().getName();
}
if (isAnyTypeSchema(schema)) {
// The 'null' value is allowed when the OAS schema is 'any type'.
// See https://github.com/OAI/OpenAPI-Specification/issues/1389
if (Boolean.FALSE.equals(schema.getNullable())) {
LOGGER.error("Schema '{}' is any type, which includes the 'null' value. 'nullable' cannot be set to 'false'", name);
}
m.isNullable = true;
}
if (ModelUtils.isArraySchema(schema)) {
m.isArray = true;
CodegenProperty arrayProperty = fromProperty(name, schema);
m.setItems(arrayProperty.items);
m.arrayModelType = arrayProperty.complexType;
addParentContainer(m, name, schema);
} else if (ModelUtils.isNullType(schema)) {
m.isNull = true;
} else if (schema instanceof ComposedSchema) {
final ComposedSchema composed = (ComposedSchema) schema;
Map properties = new LinkedHashMap();
List required = new ArrayList();
Map allProperties = new LinkedHashMap();
List allRequired = new ArrayList();
// if schema has properties outside of allOf/oneOf/anyOf also add them to m
if (composed.getProperties() != null && !composed.getProperties().isEmpty()) {
if (composed.getOneOf() != null && !composed.getOneOf().isEmpty()) {
LOGGER.warn("'oneOf' is intended to include only the additional optional OAS extension discriminator object. " +
"For more details, see https://json-schema.org/draft/2019-09/json-schema-core.html#rfc.section.9.2.1.3 and the OAS section on 'Composition and Inheritance'.");
}
addVars(m, unaliasPropertySchema(composed.getProperties()), composed.getRequired(), null, null);
}
// parent model
final String parentName = ModelUtils.getParentName(composed, allDefinitions);
final List allParents = ModelUtils.getAllParentsName(composed, allDefinitions, false);
final Schema parent = StringUtils.isBlank(parentName) || allDefinitions == null ? null : allDefinitions.get(parentName);
// TODO revise the logic below to set dicriminator, xml attributes
if (supportsInheritance || supportsMixins) {
m.allVars = new ArrayList();
if (composed.getAllOf() != null) {
int modelImplCnt = 0; // only one inline object allowed in a ComposedModel
int modelDiscriminators = 0; // only one discriminator allowed in a ComposedModel
for (Schema innerSchema : composed.getAllOf()) { // TODO need to work with anyOf, oneOf as well
if (m.discriminator == null && innerSchema.getDiscriminator() != null) {
LOGGER.debug("discriminator is set to null (not correctly set earlier): {}", name);
m.discriminator = createDiscriminator(name, innerSchema, this.openAPI);
if (!this.getLegacyDiscriminatorBehavior()) {
m.addDiscriminatorMappedModelsImports();
}
modelDiscriminators++;
}
if (innerSchema.getXml() != null) {
m.xmlPrefix = innerSchema.getXml().getPrefix();
m.xmlNamespace = innerSchema.getXml().getNamespace();
m.xmlName = innerSchema.getXml().getName();
}
if (modelDiscriminators > 1) {
LOGGER.error("Allof composed schema is inheriting >1 discriminator. Only use one discriminator: {}", composed);
}
if (modelImplCnt++ > 1) {
LOGGER.warn("More than one inline schema specified in allOf:. Only the first one is recognized. All others are ignored.");
break; // only one schema with discriminator allowed in allOf
}
}
}
}
// interfaces (schemas defined in allOf, anyOf, oneOf)
List interfaces = ModelUtils.getInterfaces(composed);
if (!interfaces.isEmpty()) {
// m.interfaces is for backward compatibility
if (m.interfaces == null)
m.interfaces = new ArrayList();
for (Schema interfaceSchema : interfaces) {
interfaceSchema = unaliasSchema(interfaceSchema, importMapping);
if (StringUtils.isBlank(interfaceSchema.get$ref())) {
// primitive type
String languageType = getTypeDeclaration(interfaceSchema);
if (composed.getAnyOf() != null) {
if (m.anyOf.contains(languageType)) {
LOGGER.warn("{} (anyOf schema) already has `{}` defined and therefore it's skipped.", m.name, languageType);
} else {
m.anyOf.add(languageType);
}
} else if (composed.getOneOf() != null) {
if (m.oneOf.contains(languageType)) {
LOGGER.warn("{} (oneOf schema) already has `{}` defined and therefore it's skipped.", m.name, languageType);
} else {
m.oneOf.add(languageType);
}
} else if (composed.getAllOf() != null) {
// no need to add primitive type to allOf, which should comprise of schemas (models) only
} else {
LOGGER.error("Composed schema has incorrect anyOf, allOf, oneOf defined: {}", composed);
}
continue;
}
// the rest of the section is for model
Schema refSchema = null;
String ref = ModelUtils.getSimpleRef(interfaceSchema.get$ref());
if (allDefinitions != null) {
refSchema = allDefinitions.get(ref);
}
final String modelName = toModelName(ref);
m.interfaces.add(modelName);
addImport(m, modelName);
if (allDefinitions != null && refSchema != null) {
if (allParents.contains(ref) && supportsMultipleInheritance) {
// multiple inheritance
addProperties(allProperties, allRequired, refSchema);
} else if (parentName != null && parentName.equals(ref) && supportsInheritance) {
// single inheritance
addProperties(allProperties, allRequired, refSchema);
} else {
// composition
addProperties(properties, required, refSchema);
addProperties(allProperties, allRequired, refSchema);
}
}
if (composed.getAnyOf() != null) {
m.anyOf.add(modelName);
} else if (composed.getOneOf() != null) {
m.oneOf.add(modelName);
} else if (composed.getAllOf() != null) {
m.allOf.add(modelName);
} else {
LOGGER.error("Composed schema has incorrect anyOf, allOf, oneOf defined: {}", composed);
}
}
}
if (parent != null && composed.getAllOf() != null) { // set parent for allOf only
m.parentSchema = parentName;
m.parent = toModelName(parentName);
if (supportsMultipleInheritance) {
m.allParents = new ArrayList();
for (String pname : allParents) {
String pModelName = toModelName(pname);
m.allParents.add(pModelName);
addImport(m, pModelName);
}
} else { // single inheritance
addImport(m, m.parent);
}
}
// child schema (properties owned by the schema itself)
for (Schema component : interfaces) {
if (component.get$ref() == null) {
if (component != null) {
// component is the child schema
addProperties(properties, required, component);
// includes child's properties (all, required) in allProperties, allRequired
addProperties(allProperties, allRequired, component);
}
break; // at most one child only
}
}
if (composed.getRequired() != null) {
required.addAll(composed.getRequired());
allRequired.addAll(composed.getRequired());
}
addVars(m, unaliasPropertySchema(properties), required, unaliasPropertySchema(allProperties), allRequired);
// Per OAS specification, composed schemas may use the 'additionalProperties' keyword.
if (supportsAdditionalPropertiesWithComposedSchema) {
// Process the schema specified with the 'additionalProperties' keyword.
// This will set the 'CodegenModel.additionalPropertiesType' field
// and potentially 'Codegen.parent'.
//
// Note: it's not a good idea to use single class inheritance to implement
// the 'additionalProperties' keyword. Code generators that use single class
// inheritance sometimes use the 'Codegen.parent' field to implement the
// 'additionalProperties' keyword. However, that would be in conflict with
// 'allOf' composed schemas, because these code generators also want to set
// 'Codegen.parent' to the first child schema of the 'allOf' schema.
addAdditionPropertiesToCodeGenModel(m, schema);
}
if (Boolean.TRUE.equals(schema.getNullable())) {
m.isNullable = Boolean.TRUE;
}
// end of code block for composed schema
} else {
m.dataType = getSchemaType(schema);
if (schema.getEnum() != null && !schema.getEnum().isEmpty()) {
m.isEnum = true;
// comment out below as allowableValues is not set in post processing model enum
m.allowableValues = new HashMap();
m.allowableValues.put("values", schema.getEnum());
}
if (ModelUtils.isMapSchema(schema)) {
addAdditionPropertiesToCodeGenModel(m, schema);
m.isMap = true;
} else if (ModelUtils.isIntegerSchema(schema)) { // integer type
// NOTE: Integral schemas as CodegenModel is a rare use case and may be removed at a later date.
m.isNumeric = Boolean.TRUE;
if (ModelUtils.isLongSchema(schema)) { // int64/long format
m.isLong = Boolean.TRUE;
} else { // int32 format
m.isInteger = Boolean.TRUE;
}
} else if (ModelUtils.isDateTimeSchema(schema)) {
// NOTE: DateTime schemas as CodegenModel is a rare use case and may be removed at a later date.
m.isDateTime = Boolean.TRUE;
} else if (ModelUtils.isDateSchema(schema)) {
// NOTE: Date schemas as CodegenModel is a rare use case and may be removed at a later date.
m.isDate = Boolean.TRUE;
} else if (ModelUtils.isStringSchema(schema)) {
// NOTE: String schemas as CodegenModel is a rare use case and may be removed at a later date.
m.isString = Boolean.TRUE;
} else if (ModelUtils.isNumberSchema(schema)) {
// NOTE: Number schemas as CodegenModel is a rare use case and may be removed at a later date.
m.isNumeric = Boolean.TRUE;
if (ModelUtils.isFloatSchema(schema)) { // float
m.isFloat = Boolean.TRUE;
} else if (ModelUtils.isDoubleSchema(schema)) { // double
m.isDouble = Boolean.TRUE;
} else { // type is number and without format
m.isNumber = Boolean.TRUE;
}
} else if (ModelUtils.isFreeFormObject(openAPI, schema)) {
addAdditionPropertiesToCodeGenModel(m, schema);
}
if (Boolean.TRUE.equals(schema.getNullable())) {
m.isNullable = Boolean.TRUE;
}
// passing null to allProperties and allRequired as there's no parent
addVars(m, unaliasPropertySchema(schema.getProperties()), schema.getRequired(), null, null);
}
// remove duplicated properties
m.removeAllDuplicatedProperty();
// set isDiscriminator on the discriminator property
if (m.discriminator != null) {
String discPropName = m.discriminator.getPropertyBaseName();
List> listOLists = new ArrayList>();
listOLists.add(m.requiredVars);
listOLists.add(m.vars);
listOLists.add(m.allVars);
for (List theseVars : listOLists) {
for (CodegenProperty requiredVar : theseVars) {
if (discPropName.equals(requiredVar.baseName)) {
requiredVar.isDiscriminator = true;
}
}
}
}
if (sortModelPropertiesByRequiredFlag) {
Comparator comparator = new Comparator() {
@Override
public int compare(CodegenProperty one, CodegenProperty another) {
if (one.required == another.required) return 0;
else if (one.required) return -1;
else return 1;
}
};
Collections.sort(m.vars, comparator);
Collections.sort(m.allVars, comparator);
}
// process 'additionalProperties'
if (schema.getAdditionalProperties() == null) {
if (disallowAdditionalPropertiesIfNotPresent) {
m.isAdditionalPropertiesTrue = false;
} else {
m.isAdditionalPropertiesTrue = true;
CodegenProperty cp = fromProperty("", new Schema());
m.setAdditionalProperties(cp);
}
} else if (schema.getAdditionalProperties() instanceof Boolean) {
if (Boolean.TRUE.equals(schema.getAdditionalProperties())) {
m.isAdditionalPropertiesTrue = true;
CodegenProperty cp = fromProperty("", new Schema());
m.setAdditionalProperties(cp);
} else {
m.isAdditionalPropertiesTrue = false;
}
} else {
m.isAdditionalPropertiesTrue = false;
CodegenProperty cp = fromProperty("", (Schema) schema.getAdditionalProperties());
m.setAdditionalProperties(cp);
}
// post process model properties
if (m.vars != null) {
for (CodegenProperty prop : m.vars) {
postProcessModelProperty(m, prop);
}
m.hasVars = m.vars.size() > 0;
}
if (m.allVars != null) {
for (CodegenProperty prop : m.allVars) {
postProcessModelProperty(m, prop);
}
}
return m;
}
/**
* Recursively look in Schema sc for the discriminator discPropName
* and return a CodegenProperty with the dataType and required params set
* the returned CodegenProperty may not be required and it may not be of type string
*
* @param composedSchemaName The name of the sc Schema
* @param sc The Schema that may contain the discriminator
* @param discPropName The String that is the discriminator propertyName in the schema
*/
private CodegenProperty discriminatorFound(String composedSchemaName, Schema sc, String discPropName, OpenAPI openAPI) {
Schema refSchema = ModelUtils.getReferencedSchema(openAPI, sc);
if (refSchema.getProperties() != null && refSchema.getProperties().get(discPropName) != null) {
Schema discSchema = (Schema) refSchema.getProperties().get(discPropName);
CodegenProperty cp = new CodegenProperty();
if (ModelUtils.isStringSchema(discSchema)) {
cp.isString = true;
}
cp.setRequired(false);
if (refSchema.getRequired() != null && refSchema.getRequired().contains(discPropName)) {
cp.setRequired(true);
}
return cp;
}
if (ModelUtils.isComposedSchema(refSchema)) {
ComposedSchema composedSchema = (ComposedSchema) refSchema;
if (composedSchema.getAllOf() != null) {
// If our discriminator is in one of the allOf schemas break when we find it
for (Schema allOf : composedSchema.getAllOf()) {
CodegenProperty cp = discriminatorFound(composedSchemaName, allOf, discPropName, openAPI);
if (cp != null) {
return cp;
}
}
}
if (composedSchema.getOneOf() != null && composedSchema.getOneOf().size() != 0) {
// All oneOf definitions must contain the discriminator
CodegenProperty cp = new CodegenProperty();
for (Schema oneOf : composedSchema.getOneOf()) {
String modelName = ModelUtils.getSimpleRef(oneOf.get$ref());
CodegenProperty thisCp = discriminatorFound(composedSchemaName, oneOf, discPropName, openAPI);
if (thisCp == null) {
throw new RuntimeException("'" + composedSchemaName + "' defines discriminator '" + discPropName + "', but the referenced OneOf schema '" + modelName + "' is missing " + discPropName);
}
if (cp.dataType == null) {
cp = thisCp;
continue;
}
if (cp != thisCp) {
throw new RuntimeException("'" + composedSchemaName + "' defines discriminator '" + discPropName + "', but the OneOf schema '" + modelName + "' has a different " + discPropName + " definition than the prior OneOf schema's. Make sure the " + discPropName + " type and required values are the same");
}
}
return cp;
}
if (composedSchema.getAnyOf() != null && composedSchema.getAnyOf().size() != 0) {
// All anyOf definitions must contain the discriminator because a min of one must be selected
CodegenProperty cp = new CodegenProperty();
for (Schema anyOf : composedSchema.getAnyOf()) {
String modelName = ModelUtils.getSimpleRef(anyOf.get$ref());
CodegenProperty thisCp = discriminatorFound(composedSchemaName, anyOf, discPropName, openAPI);
if (thisCp == null) {
throw new RuntimeException("'" + composedSchemaName + "' defines discriminator '" + discPropName + "', but the referenced AnyOf schema '" + modelName + "' is missing " + discPropName);
}
if (cp.dataType == null) {
cp = thisCp;
continue;
}
if (cp != thisCp) {
throw new RuntimeException("'" + composedSchemaName + "' defines discriminator '" + discPropName + "', but the AnyOf schema '" + modelName + "' has a different " + discPropName + " definition than the prior AnyOf schema's. Make sure the " + discPropName + " type and required values are the same");
}
}
return cp;
}
}
return null;
}
/**
* Recursively look in Schema sc for the discriminator and return it
* Schema sc location
* OpenAPI openAPI the spec where we are searching for the discriminator
*
* @param sc The Schema that may contain the discriminator
*/
private Discriminator recursiveGetDiscriminator(Schema sc, OpenAPI openAPI) {
Schema refSchema = ModelUtils.getReferencedSchema(openAPI, sc);
Discriminator foundDisc = refSchema.getDiscriminator();
if (foundDisc != null) {
return foundDisc;
}
if (this.getLegacyDiscriminatorBehavior()) {
return null;
}
Discriminator disc = new Discriminator();
if (ModelUtils.isComposedSchema(refSchema)) {
ComposedSchema composedSchema = (ComposedSchema) refSchema;
if (composedSchema.getAllOf() != null) {
// If our discriminator is in one of the allOf schemas break when we find it
for (Schema allOf : composedSchema.getAllOf()) {
foundDisc = recursiveGetDiscriminator(allOf, openAPI);
if (foundDisc != null) {
disc.setPropertyName(foundDisc.getPropertyName());
disc.setMapping(foundDisc.getMapping());
return disc;
}
}
}
if (composedSchema.getOneOf() != null && composedSchema.getOneOf().size() != 0) {
// All oneOf definitions must contain the discriminator
Integer hasDiscriminatorCnt = 0;
Integer hasNullTypeCnt = 0;
Set discriminatorsPropNames = new HashSet<>();
for (Schema oneOf : composedSchema.getOneOf()) {
if (ModelUtils.isNullType(oneOf)) {
// The null type does not have a discriminator. Skip.
hasNullTypeCnt++;
continue;
}
foundDisc = recursiveGetDiscriminator(oneOf, openAPI);
if (foundDisc != null) {
discriminatorsPropNames.add(foundDisc.getPropertyName());
hasDiscriminatorCnt++;
}
}
if (discriminatorsPropNames.size() > 1) {
throw new RuntimeException("The oneOf schemas have conflicting discriminator property names. " +
"oneOf schemas must have the same property name, but found " + String.join(", ", discriminatorsPropNames));
}
if (foundDisc != null && (hasDiscriminatorCnt + hasNullTypeCnt) == composedSchema.getOneOf().size() && discriminatorsPropNames.size() == 1) {
disc.setPropertyName(foundDisc.getPropertyName());
disc.setMapping(foundDisc.getMapping());
return disc;
}
// If the scenario when oneOf has two children and one of them is the 'null' type,
// there is no need for a discriminator.
}
if (composedSchema.getAnyOf() != null && composedSchema.getAnyOf().size() != 0) {
// All anyOf definitions must contain the discriminator because a min of one must be selected
Integer hasDiscriminatorCnt = 0;
Integer hasNullTypeCnt = 0;
Set discriminatorsPropNames = new HashSet<>();
for (Schema anyOf : composedSchema.getAnyOf()) {
if (ModelUtils.isNullType(anyOf)) {
// The null type does not have a discriminator. Skip.
hasNullTypeCnt++;
continue;
}
foundDisc = recursiveGetDiscriminator(anyOf, openAPI);
if (foundDisc != null) {
discriminatorsPropNames.add(foundDisc.getPropertyName());
hasDiscriminatorCnt++;
}
}
if (discriminatorsPropNames.size() > 1) {
throw new RuntimeException("The anyOf schemas have conflicting discriminator property names. " +
"anyOf schemas must have the same property name, but found " + String.join(", ", discriminatorsPropNames));
}
if (foundDisc != null && (hasDiscriminatorCnt + hasNullTypeCnt) == composedSchema.getAnyOf().size() && discriminatorsPropNames.size() == 1) {
disc.setPropertyName(foundDisc.getPropertyName());
disc.setMapping(foundDisc.getMapping());
return disc;
}
// If the scenario when anyOf has two children and one of them is the 'null' type,
// there is no need for a discriminator.
}
}
return null;
}
/**
* This function is only used for composed schemas which have a discriminator
* Process oneOf and anyOf models in a composed schema and adds them into
* a list if the oneOf and anyOf models contain
* the required discriminator. If they don't contain the required
* discriminator or the discriminator is the wrong type then an error is
* thrown
*
* @param composedSchemaName The String model name of the composed schema where we are setting the discriminator map
* @param discPropName The String that is the discriminator propertyName in the schema
* @param c The ComposedSchema that contains the discriminator and oneOf/anyOf schemas
* @param openAPI The OpenAPI spec that we are using
* @return the list of oneOf and anyOf MappedModel that need to be added to the discriminator map
*/
protected List getOneOfAnyOfDescendants(String composedSchemaName, String discPropName, ComposedSchema c, OpenAPI openAPI) {
ArrayList> listOLists = new ArrayList>();
listOLists.add(c.getOneOf());
listOLists.add(c.getAnyOf());
List descendentSchemas = new ArrayList();
for (List schemaList : listOLists) {
if (schemaList == null) {
continue;
}
for (Schema sc : schemaList) {
if (ModelUtils.isNullType(sc)) {
continue;
}
String ref = sc.get$ref();
if (ref == null) {
// for schemas with no ref, it is not possible to build the discriminator map
// because ref is how we get the model name
// we only hit this use case for a schema with inline composed schemas, and one of those
// schemas also has inline composed schemas
// Note: if it is only inline one level, then the inline model resolver will move it into its own
// schema and make it a $ref schema in the oneOf/anyOf location
throw new RuntimeException("Invalid inline schema defined in oneOf/anyOf in '" + composedSchemaName + "'. Per the OpenApi spec, for this case when a composed schema defines a discriminator, the oneOf/anyOf schemas must use $ref. Change this inline definition to a $ref definition");
}
CodegenProperty df = discriminatorFound(composedSchemaName, sc, discPropName, openAPI);
String modelName = ModelUtils.getSimpleRef(ref);
if (df == null || !df.isString || df.required != true) {
String msgSuffix = "";
if (df == null) {
msgSuffix += discPropName + " is missing from the schema, define it as required and type string";
} else {
if (!df.isString) {
msgSuffix += "invalid type for " + discPropName + ", set it to string";
}
if (df.required != true) {
String spacer = "";
if (msgSuffix.length() != 0) {
spacer = ". ";
}
msgSuffix += spacer + "invalid optional definition of " + discPropName + ", include it in required";
}
}
throw new RuntimeException("'" + composedSchemaName + "' defines discriminator '" + discPropName + "', but the referenced schema '" + modelName + "' is incorrect. " + msgSuffix);
}
MappedModel mm = new MappedModel(modelName, toModelName(modelName));
descendentSchemas.add(mm);
Schema cs = ModelUtils.getSchema(openAPI, modelName);
Map vendorExtensions = cs.getExtensions();
if (vendorExtensions != null && !vendorExtensions.isEmpty() && vendorExtensions.containsKey("x-discriminator-value")) {
String xDiscriminatorValue = (String) vendorExtensions.get("x-discriminator-value");
mm = new MappedModel(xDiscriminatorValue, toModelName(modelName));
descendentSchemas.add(mm);
}
}
}
return descendentSchemas;
}
protected List getAllOfDescendants(String thisSchemaName, OpenAPI openAPI) {
ArrayList queue = new ArrayList();
List descendentSchemas = new ArrayList();
Map schemas = ModelUtils.getSchemas(openAPI);
String currentSchemaName = thisSchemaName;
Set keys = schemas.keySet();
int count = 0;
// hack: avoid infinite loop on potential self-references in event our checks fail.
while (100000 > count++) {
for (String childName : keys) {
if (childName.equals(thisSchemaName)) {
continue;
}
Schema child = schemas.get(childName);
if (ModelUtils.isComposedSchema(child)) {
ComposedSchema composedChild = (ComposedSchema) child;
List parents = composedChild.getAllOf();
if (parents != null) {
for (Schema parent : parents) {
String ref = parent.get$ref();
if (ref == null) {
// for schemas with no ref, it is not possible to build the discriminator map
// because ref is how we get the model name
// we hit this use case when an allOf composed schema contains an inline schema
continue;
}
String parentName = ModelUtils.getSimpleRef(ref);
if (parentName != null && parentName.equals(currentSchemaName)) {
if (queue.contains(childName) || descendentSchemas.stream().anyMatch(i -> childName.equals(i.getMappingName()))) {
throw new RuntimeException("Stack overflow hit when looking for " + thisSchemaName + " an infinite loop starting and ending at " + childName + " was seen");
}
queue.add(childName);
break;
}
}
}
}
}
if (queue.size() == 0) {
break;
}
currentSchemaName = queue.remove(0);
MappedModel mm = new MappedModel(currentSchemaName, toModelName(currentSchemaName));
descendentSchemas.add(mm);
Schema cs = schemas.get(currentSchemaName);
Map vendorExtensions = cs.getExtensions();
if (vendorExtensions != null && !vendorExtensions.isEmpty() && vendorExtensions.containsKey("x-discriminator-value")) {
String xDiscriminatorValue = (String) vendorExtensions.get("x-discriminator-value");
mm = new MappedModel(xDiscriminatorValue, toModelName(currentSchemaName));
descendentSchemas.add(mm);
}
}
return descendentSchemas;
}
protected CodegenDiscriminator createDiscriminator(String schemaName, Schema schema, OpenAPI openAPI) {
Discriminator sourceDiscriminator = recursiveGetDiscriminator(schema, openAPI);
if (sourceDiscriminator == null) {
return null;
}
CodegenDiscriminator discriminator = new CodegenDiscriminator();
String discPropName = sourceDiscriminator.getPropertyName();
discriminator.setPropertyName(toVarName(discPropName));
discriminator.setPropertyBaseName(sourceDiscriminator.getPropertyName());
discriminator.setPropertyGetter(toGetter(discriminator.getPropertyName()));
// FIXME: for now, we assume that the discriminator property is String
discriminator.setPropertyType(typeMapping.get("string"));
discriminator.setMapping(sourceDiscriminator.getMapping());
List uniqueDescendants = new ArrayList();
if (sourceDiscriminator.getMapping() != null && !sourceDiscriminator.getMapping().isEmpty()) {
for (Entry e : sourceDiscriminator.getMapping().entrySet()) {
String nameOrRef = e.getValue();
String name = nameOrRef.indexOf('/') >= 0 ? ModelUtils.getSimpleRef(nameOrRef) : nameOrRef;
String modelName = toModelName(name);
uniqueDescendants.add(new MappedModel(e.getKey(), modelName));
}
}
boolean legacyUseCase = (this.getLegacyDiscriminatorBehavior() && uniqueDescendants.isEmpty());
if (!this.getLegacyDiscriminatorBehavior() || legacyUseCase) {
// for schemas that allOf inherit from this schema, add those descendants to this discriminator map
List otherDescendants = getAllOfDescendants(schemaName, openAPI);
for (MappedModel otherDescendant : otherDescendants) {
// add only if the mapping names are not the same
boolean matched = false;
for (MappedModel uniqueDescendant: uniqueDescendants) {
if (uniqueDescendant.getMappingName().equals(otherDescendant.getMappingName())) {
matched = true;
break;
}
}
if (matched == false) {
uniqueDescendants.add(otherDescendant);
}
}
}
// if there are composed oneOf/anyOf schemas, add them to this discriminator
if (ModelUtils.isComposedSchema(schema) && !this.getLegacyDiscriminatorBehavior()) {
List otherDescendants = getOneOfAnyOfDescendants(schemaName, discPropName, (ComposedSchema) schema, openAPI);
for (MappedModel otherDescendant : otherDescendants) {
if (!uniqueDescendants.contains(otherDescendant)) {
uniqueDescendants.add(otherDescendant);
}
}
}
if (!this.getLegacyDiscriminatorBehavior()) {
Collections.sort(uniqueDescendants);
}
discriminator.getMappedModels().addAll(uniqueDescendants);
return discriminator;
}
/**
* Handle the model for the 'additionalProperties' keyword in the OAS schema.
*
* @param codegenModel The codegen representation of the schema.
* @param schema The input OAS schema.
*/
protected void addAdditionPropertiesToCodeGenModel(CodegenModel codegenModel, Schema schema) {
addParentContainer(codegenModel, codegenModel.name, schema);
}
/**
* Add schema's properties to "properties" and "required" list
*
* @param properties all properties
* @param required required property only
* @param schema schema in which the properties will be added to the lists
*/
protected void addProperties(Map properties, List required, Schema schema) {
if (schema instanceof ComposedSchema) {
ComposedSchema composedSchema = (ComposedSchema) schema;
if (composedSchema.getAllOf() != null) {
for (Schema component : composedSchema.getAllOf()) {
addProperties(properties, required, component);
}
}
if (schema.getRequired() != null) {
required.addAll(schema.getRequired());
}
if (composedSchema.getOneOf() != null) {
for (Schema component : composedSchema.getOneOf()) {
addProperties(properties, required, component);
}
}
if (composedSchema.getAnyOf() != null) {
for (Schema component : composedSchema.getAnyOf()) {
addProperties(properties, required, component);
}
}
return;
}
if (StringUtils.isNotBlank(schema.get$ref())) {
Schema interfaceSchema = ModelUtils.getReferencedSchema(this.openAPI, schema);
addProperties(properties, required, interfaceSchema);
return;
}
if (schema.getProperties() != null) {
properties.putAll(schema.getProperties());
}
if (schema.getRequired() != null) {
required.addAll(schema.getRequired());
}
}
/**
* Camelize the method name of the getter and setter
*
* @param name string to be camelized
* @return Camelized string
*/
public String getterAndSetterCapitalize(String name) {
if (name == null || name.length() == 0) {
return name;
}
return camelize(toVarName(name));
}
/**
* Convert OAS Property object to Codegen Property object.
*
* The return value is cached. An internal cache is looked up to determine
* if the CodegenProperty return value has already been instantiated for
* the (String name, Schema p) arguments.
* Any subsequent processing of the CodegenModel return value must be idempotent
* for a given (String name, Schema schema).
*
* @param name name of the property
* @param p OAS property schema
* @return Codegen Property object
*/
public CodegenProperty fromProperty(String name, Schema p) {
if (p == null) {
LOGGER.error("Undefined property/schema for `{}`. Default to type:string.", name);
return null;
}
LOGGER.debug("debugging fromProperty for " + name + " : " + p);
NamedSchema ns = new NamedSchema(name, p);
CodegenProperty cpc = schemaCodegenPropertyCache.get(ns);
if (cpc != null) {
LOGGER.debug("Cached fromProperty for " + name + " : " + p.getName());
return cpc;
}
// unalias schema
p = unaliasSchema(p, importMapping);
CodegenProperty property = CodegenModelFactory.newInstance(CodegenModelType.PROPERTY);
ModelUtils.syncValidationProperties(p, property);
property.name = toVarName(name);
property.baseName = name;
if (p.getType() == null) {
property.openApiType = getSchemaType(p);
} else {
property.openApiType = p.getType();
}
property.nameInCamelCase = camelize(property.name, false);
property.nameInSnakeCase = CaseFormat.UPPER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, property.nameInCamelCase);
property.description = escapeText(p.getDescription());
property.unescapedDescription = p.getDescription();
property.title = p.getTitle();
property.getter = toGetter(name);
property.setter = toSetter(name);
property.example = toExampleValue(p);
property.defaultValue = toDefaultValue(p);
property.defaultValueWithParam = toDefaultValueWithParam(name, p);
property.jsonSchema = Json.pretty(p);
if (p.getDeprecated() != null) {
property.deprecated = p.getDeprecated();
} else if (p.get$ref() != null) {
// Since $ref should be replaced with the model it refers
// to, $ref'ing a model with 'deprecated' set should cause
// the property to reflect the model's 'deprecated' value.
String ref = ModelUtils.getSimpleRef(p.get$ref());
if (ref != null) {
Schema referencedSchema = ModelUtils.getSchemas(this.openAPI).get(ref);
if (referencedSchema != null && referencedSchema.getDeprecated() != null) {
property.deprecated = referencedSchema.getDeprecated();
}
}
}
if (p.getReadOnly() != null) {
property.isReadOnly = p.getReadOnly();
}
if (p.getWriteOnly() != null) {
property.isWriteOnly = p.getWriteOnly();
}
if (p.getNullable() != null) {
property.isNullable = p.getNullable();
}
if (p.getXml() != null) {
if (p.getXml().getAttribute() != null) {
property.isXmlAttribute = p.getXml().getAttribute();
}
property.xmlPrefix = p.getXml().getPrefix();
property.xmlName = p.getXml().getName();
property.xmlNamespace = p.getXml().getNamespace();
}
if (p.getExtensions() != null && !p.getExtensions().isEmpty()) {
property.getVendorExtensions().putAll(p.getExtensions());
}
String type = getSchemaType(p);
if (ModelUtils.isIntegerSchema(p)) { // integer type
property.isNumeric = Boolean.TRUE;
if (ModelUtils.isLongSchema(p)) { // int64/long format
property.isLong = Boolean.TRUE;
} else { // int32 format
property.isInteger = Boolean.TRUE;
}
} else if (ModelUtils.isBooleanSchema(p)) { // boolean type
property.isBoolean = true;
property.getter = toBooleanGetter(name);
} else if (ModelUtils.isDateSchema(p)) { // date format
property.isString = false; // for backward compatibility with 2.x
property.isDate = true;
} else if (ModelUtils.isDateTimeSchema(p)) { // date-time format
property.isString = false; // for backward compatibility with 2.x
property.isDateTime = true;
} else if (ModelUtils.isDecimalSchema(p)) { // type: string, format: number
property.isDecimal = true;
} else if (ModelUtils.isStringSchema(p)) {
if (ModelUtils.isByteArraySchema(p)) {
property.isByteArray = true;
} else if (ModelUtils.isBinarySchema(p)) {
property.isBinary = true;
property.isFile = true; // file = binary in OAS3
} else if (ModelUtils.isFileSchema(p)) {
property.isFile = true;
} else if (ModelUtils.isUUIDSchema(p)) {
// keep isString to true to make it backward compatible
property.isString = true;
property.isUuid = true;
} else if (ModelUtils.isURISchema(p)) {
property.isString = true; // for backward compatibility
property.isUri = true;
} else if (ModelUtils.isEmailSchema(p)) {
property.isString = true;
property.isEmail = true;
} else {
property.isString = true;
}
property.pattern = toRegularExpression(p.getPattern());
} else if (ModelUtils.isNumberSchema(p)) {
property.isNumeric = Boolean.TRUE;
if (ModelUtils.isFloatSchema(p)) { // float
property.isFloat = Boolean.TRUE;
} else if (ModelUtils.isDoubleSchema(p)) { // double
property.isDouble = Boolean.TRUE;
} else { // type is number and without format
property.isNumber = Boolean.TRUE;
}
} else if (isFreeFormObject(p)) {
property.isFreeFormObject = true;
} else if (isAnyTypeSchema(p)) {
// The 'null' value is allowed when the OAS schema is 'any type'.
// See https://github.com/OAI/OpenAPI-Specification/issues/1389
if (Boolean.FALSE.equals(p.getNullable())) {
LOGGER.warn("Schema '{}' is any type, which includes the 'null' value. 'nullable' cannot be set to 'false'", p.getName());
}
property.isNullable = true;
property.isAnyType = true;
} else if (ModelUtils.isArraySchema(p)) {
// default to string if inner item is undefined
ArraySchema arraySchema = (ArraySchema) p;
Schema innerSchema = unaliasSchema(getSchemaItems(arraySchema), importMapping);
} else if (ModelUtils.isMapSchema(p)) {
Schema innerSchema = unaliasSchema(getAdditionalProperties(p), importMapping);
if (innerSchema == null) {
LOGGER.error("Undefined map inner type for `{}`. Default to String.", p.getName());
innerSchema = new StringSchema().description("//TODO automatically added by openapi-generator due to undefined type");
p.setAdditionalProperties(innerSchema);
}
} else if (ModelUtils.isNullType(p)) {
property.isNull = true;
}
//Inline enum case:
if (p.getEnum() != null && !p.getEnum().isEmpty()) {
List _enum = p.getEnum();
property._enum = new ArrayList();
for (Object i : _enum) {
property._enum.add(String.valueOf(i));
}
property.isEnum = true;
Map allowableValues = new HashMap();
allowableValues.put("values", _enum);
if (allowableValues.size() > 0) {
property.allowableValues = allowableValues;
}
}
Schema referencedSchema = ModelUtils.getReferencedSchema(this.openAPI, p);
//Referenced enum case:
if (referencedSchema.getEnum() != null && !referencedSchema.getEnum().isEmpty()) {
List _enum = referencedSchema.getEnum();
Map allowableValues = new HashMap();
allowableValues.put("values", _enum);
if (allowableValues.size() > 0) {
property.allowableValues = allowableValues;
}
}
if (referencedSchema.getNullable() != null) {
property.isNullable = referencedSchema.getNullable();
}
property.dataType = getTypeDeclaration(p);
property.dataFormat = p.getFormat();
property.baseType = getSchemaType(p);
// this can cause issues for clients which don't support enums
if (property.isEnum) {
property.datatypeWithEnum = toEnumName(property);
property.enumName = toEnumName(property);
} else {
property.datatypeWithEnum = property.dataType;
}
if (ModelUtils.isArraySchema(p)) {
property.isContainer = true;
property.isArray = true;
if (ModelUtils.isSet(p)) {
property.containerType = "set";
} else {
property.containerType = "array";
}
property.baseType = getSchemaType(p);
if (p.getXml() != null) {
property.isXmlWrapped = p.getXml().getWrapped() == null ? false : p.getXml().getWrapped();
property.xmlPrefix = p.getXml().getPrefix();
property.xmlNamespace = p.getXml().getNamespace();
property.xmlName = p.getXml().getName();
}
// handle inner property
String itemName = null;
if (p.getExtensions() != null && p.getExtensions().get("x-item-name") != null) {
itemName = p.getExtensions().get("x-item-name").toString();
}
if (itemName == null) {
itemName = property.name;
}
ArraySchema arraySchema = (ArraySchema) p;
Schema innerSchema = unaliasSchema(getSchemaItems(arraySchema), importMapping);
CodegenProperty cp = fromProperty(itemName, innerSchema);
updatePropertyForArray(property, cp);
} else if (ModelUtils.isMapSchema(p)) {
property.isContainer = true;
property.isMap = true;
property.containerType = "map";
property.baseType = getSchemaType(p);
// TODO remove this hack in the future, code should use minProperties and maxProperties for object schemas
property.minItems = p.getMinProperties();
property.maxItems = p.getMaxProperties();
// handle inner property
Schema innerSchema = unaliasSchema(getAdditionalProperties(p), importMapping);
if (innerSchema == null) {
LOGGER.error("Undefined map inner type for `{}`. Default to String.", p.getName());
innerSchema = new StringSchema().description("//TODO automatically added by openapi-generator due to undefined type");
p.setAdditionalProperties(innerSchema);
}
CodegenProperty cp = fromProperty("inner", innerSchema);
updatePropertyForMap(property, cp);
} else if (isFreeFormObject(p)) {
property.isFreeFormObject = true;
property.baseType = getSchemaType(p);
if (languageSpecificPrimitives.contains(property.dataType)) {
property.isPrimitiveType = true;
}
} else if (isAnyTypeSchema(p)) {
property.isAnyType = true;
property.baseType = getSchemaType(p);
if (languageSpecificPrimitives.contains(property.dataType)) {
property.isPrimitiveType = true;
}
} else { // model
setNonArrayMapProperty(property, type);
Schema refOrCurrent = ModelUtils.getReferencedSchema(this.openAPI, p);
property.isModel = (ModelUtils.isComposedSchema(refOrCurrent) || ModelUtils.isObjectSchema(refOrCurrent)) && ModelUtils.isModel(refOrCurrent);
}
addVarsRequiredVarsAdditionaProps(p, property);
LOGGER.debug("debugging from property return: " + property);
schemaCodegenPropertyCache.put(ns, property);
return property;
}
/**
* Update property for array(list) container
*
* @param property Codegen property
* @param innerProperty Codegen inner property of map or list
*/
protected void updatePropertyForArray(CodegenProperty property, CodegenProperty innerProperty) {
if (innerProperty == null) {
LOGGER.warn("skipping invalid array property " + Json.pretty(property));
return;
}
property.dataFormat = innerProperty.dataFormat;
if (!languageSpecificPrimitives.contains(innerProperty.baseType)) {
property.complexType = innerProperty.baseType;
} else {
property.isPrimitiveType = true;
}
property.items = innerProperty;
property.mostInnerItems = getMostInnerItems(innerProperty);
// inner item is Enum
if (isPropertyInnerMostEnum(property)) {
// isEnum is set to true when the type is an enum
// or the inner type of an array/map is an enum
property.isEnum = true;
// update datatypeWithEnum and default value for array
// e.g. List => List
updateDataTypeWithEnumForArray(property);
// set allowable values to enum values (including array/map of enum)
property.allowableValues = getInnerEnumAllowableValues(property);
}
}
/**
* Update property for map container
*
* @param property Codegen property
* @param innerProperty Codegen inner property of map or list
*/
protected void updatePropertyForMap(CodegenProperty property, CodegenProperty innerProperty) {
if (innerProperty == null) {
LOGGER.warn("skipping invalid map property " + Json.pretty(property));
return;
}
if (!languageSpecificPrimitives.contains(innerProperty.baseType)) {
property.complexType = innerProperty.baseType;
} else {
property.isPrimitiveType = true;
}
property.items = innerProperty;
property.mostInnerItems = getMostInnerItems(innerProperty);
property.dataFormat = innerProperty.dataFormat;
// inner item is Enum
if (isPropertyInnerMostEnum(property)) {
// isEnum is set to true when the type is an enum
// or the inner type of an array/map is an enum
property.isEnum = true;
// update datatypeWithEnum and default value for map
// e.g. Dictionary => Dictionary
updateDataTypeWithEnumForMap(property);
// set allowable values to enum values (including array/map of enum)
property.allowableValues = getInnerEnumAllowableValues(property);
}
}
/**
* Update property for map container
*
* @param property Codegen property
* @return True if the inner most type is enum
*/
protected Boolean isPropertyInnerMostEnum(CodegenProperty property) {
CodegenProperty currentProperty = getMostInnerItems(property);
return currentProperty == null ? false : currentProperty.isEnum;
}
protected CodegenProperty getMostInnerItems(CodegenProperty property) {
CodegenProperty currentProperty = property;
while (currentProperty != null && (Boolean.TRUE.equals(currentProperty.isMap)
|| Boolean.TRUE.equals(currentProperty.isArray))) {
currentProperty = currentProperty.items;
}
return currentProperty;
}
protected Map getInnerEnumAllowableValues(CodegenProperty property) {
CodegenProperty currentProperty = getMostInnerItems(property);
return currentProperty == null ? new HashMap() : currentProperty.allowableValues;
}
/**
* Update datatypeWithEnum for array container
*
* @param property Codegen property
*/
protected void updateDataTypeWithEnumForArray(CodegenProperty property) {
CodegenProperty baseItem = property.items;
while (baseItem != null && (Boolean.TRUE.equals(baseItem.isMap)
|| Boolean.TRUE.equals(baseItem.isArray))) {
baseItem = baseItem.items;
}
if (baseItem != null) {
// set both datatype and datetypeWithEnum as only the inner type is enum
property.datatypeWithEnum = property.datatypeWithEnum.replace(baseItem.baseType, toEnumName(baseItem));
// naming the enum with respect to the language enum naming convention
// e.g. remove [], {} from array/map of enum
property.enumName = toEnumName(property);
// set default value for variable with inner enum
if (property.defaultValue != null) {
property.defaultValue = property.defaultValue.replace(baseItem.baseType, toEnumName(baseItem));
}
updateCodegenPropertyEnum(property);
}
}
/**
* Update datatypeWithEnum for map container
*
* @param property Codegen property
*/
protected void updateDataTypeWithEnumForMap(CodegenProperty property) {
CodegenProperty baseItem = property.items;
while (baseItem != null && (Boolean.TRUE.equals(baseItem.isMap)
|| Boolean.TRUE.equals(baseItem.isArray))) {
baseItem = baseItem.items;
}
if (baseItem != null) {
// set both datatype and datetypeWithEnum as only the inner type is enum
property.datatypeWithEnum = property.datatypeWithEnum.replace(", " + baseItem.baseType, ", " + toEnumName(baseItem));
// naming the enum with respect to the language enum naming convention
// e.g. remove [], {} from array/map of enum
property.enumName = toEnumName(property);
// set default value for variable with inner enum
if (property.defaultValue != null) {
property.defaultValue = property.defaultValue.replace(", " + property.items.baseType, ", " + toEnumName(property.items));
}
updateCodegenPropertyEnum(property);
}
}
protected void setNonArrayMapProperty(CodegenProperty property, String type) {
property.isContainer = false;
if (languageSpecificPrimitives().contains(type)) {
property.isPrimitiveType = true;
} else {
property.complexType = property.baseType;
property.isModel = true;
}
}
/**
* Override with any special handling of response codes
*
* @param responses OAS Operation's responses
* @return default method response or null
if not found
*/
protected ApiResponse findMethodResponse(ApiResponses responses) {
String code = null;
for (String responseCode : responses.keySet()) {
if (responseCode.startsWith("2") || responseCode.equals("default")) {
if (code == null || code.compareTo(responseCode) > 0) {
code = responseCode;
}
}
}
if (code == null) {
return null;
}
return responses.get(code);
}
/**
* Set op's returnBaseType, returnType, examples etc.
*
* @param operation endpoint Operation
* @param schemas a map of the schemas in the openapi spec
* @param op endpoint CodegenOperation
* @param methodResponse the default ApiResponse for the endpoint
*/
protected void handleMethodResponse(Operation operation,
Map schemas,
CodegenOperation op,
ApiResponse methodResponse) {
handleMethodResponse(operation, schemas, op, methodResponse, Collections.emptyMap());
}
/**
* Set op's returnBaseType, returnType, examples etc.
*
* @param operation endpoint Operation
* @param schemas a map of the schemas in the openapi spec
* @param op endpoint CodegenOperation
* @param methodResponse the default ApiResponse for the endpoint
* @param importMappings mappings of external types to be omitted by unaliasing
*/
protected void handleMethodResponse(Operation operation,
Map schemas,
CodegenOperation op,
ApiResponse methodResponse,
Map importMappings) {
Schema responseSchema = unaliasSchema(ModelUtils.getSchemaFromResponse(methodResponse), importMapping);
if (responseSchema != null) {
CodegenProperty cm = fromProperty("response", responseSchema);
if (ModelUtils.isArraySchema(responseSchema)) {
ArraySchema as = (ArraySchema) responseSchema;
CodegenProperty innerProperty = fromProperty("response", getSchemaItems(as));
op.returnBaseType = innerProperty.baseType;
} else if (ModelUtils.isMapSchema(responseSchema)) {
CodegenProperty innerProperty = fromProperty("response", getAdditionalProperties(responseSchema));
op.returnBaseType = innerProperty.baseType;
} else {
if (cm.complexType != null) {
op.returnBaseType = cm.complexType;
} else {
op.returnBaseType = cm.baseType;
}
}
// generate examples
String exampleStatusCode = "200";
for (String key : operation.getResponses().keySet()) {
if (operation.getResponses().get(key) == methodResponse && !key.equals("default")) {
exampleStatusCode = key;
}
}
op.examples = new ExampleGenerator(schemas, this.openAPI).generateFromResponseSchema(exampleStatusCode, responseSchema, getProducesInfo(this.openAPI, operation));
op.defaultResponse = toDefaultValue(responseSchema);
op.returnType = cm.dataType;
op.returnFormat = cm.dataFormat;
op.hasReference = schemas != null && schemas.containsKey(op.returnBaseType);
// lookup discriminator
Schema schema = null;
if (schemas != null) {
schema = schemas.get(op.returnBaseType);
}
if (schema != null) {
CodegenModel cmod = fromModel(op.returnBaseType, schema);
op.discriminator = cmod.discriminator;
}
if (cm.isContainer) {
op.returnContainer = cm.containerType;
if ("map".equals(cm.containerType)) {
op.isMap = true;
} else if ("list".equalsIgnoreCase(cm.containerType)) {
op.isArray = true;
} else if ("array".equalsIgnoreCase(cm.containerType)) {
op.isArray = true;
} else if ("set".equalsIgnoreCase(cm.containerType)) {
op.isArray = true;
}
} else {
op.returnSimpleType = true;
}
if (languageSpecificPrimitives().contains(op.returnBaseType) || op.returnBaseType == null) {
op.returnTypeIsPrimitive = true;
}
}
addHeaders(methodResponse, op.responseHeaders);
}
/**
* Convert OAS Operation object to Codegen Operation object
*
* @param httpMethod HTTP method
* @param operation OAS operation object
* @param path the path of the operation
* @param servers list of servers
* @return Codegen Operation object
*/
public CodegenOperation fromOperation(String path,
String httpMethod,
Operation operation,
List servers) {
LOGGER.debug("fromOperation => operation: " + operation);
if (operation == null)
throw new RuntimeException("operation cannot be null in fromOperation");
Map schemas = ModelUtils.getSchemas(this.openAPI);
CodegenOperation op = CodegenModelFactory.newInstance(CodegenModelType.OPERATION);
Set imports = new HashSet();
if (operation.getExtensions() != null && !operation.getExtensions().isEmpty()) {
op.vendorExtensions.putAll(operation.getExtensions());
Object isCallbackRequest = op.vendorExtensions.remove("x-callback-request");
op.isCallbackRequest = Boolean.TRUE.equals(isCallbackRequest);
}
// servers setting
if (operation.getServers() != null && !operation.getServers().isEmpty()) {
// use operation-level servers first if defined
op.servers = fromServers(operation.getServers());
} else if (servers != null && !servers.isEmpty()) {
// use path-level servers
op.servers = fromServers(servers);
}
// store the original operationId for plug-in
op.operationIdOriginal = operation.getOperationId();
String operationId = getOrGenerateOperationId(operation, path, httpMethod);
// remove prefix in operationId
if (removeOperationIdPrefix) {
int offset = operationId.indexOf('_');
if (offset > -1) {
operationId = operationId.substring(offset + 1);
}
}
operationId = removeNonNameElementToCamelCase(operationId);
if (isStrictSpecBehavior() && !path.startsWith("/")) {
// modifies an operation.path to strictly conform to OpenAPI Spec
op.path = "/" + path;
} else {
op.path = path;
}
op.operationId = toOperationId(operationId);
op.summary = escapeText(operation.getSummary());
op.unescapedNotes = operation.getDescription();
op.notes = escapeText(operation.getDescription());
op.hasConsumes = false;
op.hasProduces = false;
if (operation.getDeprecated() != null) {
op.isDeprecated = operation.getDeprecated();
}
addConsumesInfo(operation, op);
if (operation.getResponses() != null && !operation.getResponses().isEmpty()) {
ApiResponse methodResponse = findMethodResponse(operation.getResponses());
for (String key : operation.getResponses().keySet()) {
ApiResponse response = operation.getResponses().get(key);
addProducesInfo(response, op);
CodegenResponse r = fromResponse(key, response);
if (r.baseType != null &&
!defaultIncludes.contains(r.baseType) &&
!languageSpecificPrimitives.contains(r.baseType)) {
imports.add(r.baseType);
}
if ("set".equals(r.containerType) && typeMapping.containsKey(r.containerType)) {
op.uniqueItems = true;
imports.add(typeMapping.get(r.containerType));
}
op.responses.add(r);
if (Boolean.TRUE.equals(r.isBinary) && Boolean.TRUE.equals(r.is2xx) && Boolean.FALSE.equals(op.isResponseBinary)) {
op.isResponseBinary = Boolean.TRUE;
}
if (Boolean.TRUE.equals(r.isFile) && Boolean.TRUE.equals(r.is2xx) && Boolean.FALSE.equals(op.isResponseFile)) {
op.isResponseFile = Boolean.TRUE;
}
}
op.responses.sort((a, b) -> {
int aDefault = "0".equals(a.code) ? 1 : 0;
int bDefault = "0".equals(b.code) ? 1 : 0;
return aDefault - bDefault;
});
if (methodResponse != null) {
handleMethodResponse(operation, schemas, op, methodResponse, importMapping);
}
}
if (operation.getCallbacks() != null && !operation.getCallbacks().isEmpty()) {
operation.getCallbacks().forEach((name, callback) -> {
CodegenCallback c = fromCallback(name, callback, servers);
op.callbacks.add(c);
});
}
List parameters = operation.getParameters();
List allParams = new ArrayList();
List bodyParams = new ArrayList();
List pathParams = new ArrayList();
List queryParams = new ArrayList();
List headerParams = new ArrayList();
List