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 2017-2018 original authors
*
* 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.Compiler;
import com.samskivert.mustache.Mustache.Lambda;
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.Content;
import io.swagger.v3.oas.models.media.Discriminator;
import io.swagger.v3.oas.models.media.Encoding;
import io.swagger.v3.oas.models.media.MediaType;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.media.StringSchema;
import io.swagger.v3.oas.models.media.XML;
import io.swagger.v3.oas.models.parameters.CookieParameter;
import io.swagger.v3.oas.models.parameters.HeaderParameter;
import io.swagger.v3.oas.models.parameters.Parameter;
import io.swagger.v3.oas.models.parameters.PathParameter;
import io.swagger.v3.oas.models.parameters.QueryParameter;
import io.swagger.v3.oas.models.parameters.RequestBody;
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 org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.text.StringEscapeUtils;
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.languages.PhpNextgenClientCodegen;
import org.openapitools.codegen.languages.RustAxumServerCodegen;
import org.openapitools.codegen.languages.RustServerCodegen;
import org.openapitools.codegen.meta.FeatureSet;
import org.openapitools.codegen.meta.GeneratorMetadata;
import org.openapitools.codegen.meta.Stability;
import org.openapitools.codegen.meta.features.DataTypeFeature;
import org.openapitools.codegen.meta.features.DocumentationFeature;
import org.openapitools.codegen.meta.features.GlobalFeature;
import org.openapitools.codegen.meta.features.ParameterFeature;
import org.openapitools.codegen.meta.features.SchemaSupportFeature;
import org.openapitools.codegen.meta.features.SecurityFeature;
import org.openapitools.codegen.meta.features.WireFormatFeature;
import org.openapitools.codegen.model.ModelMap;
import org.openapitools.codegen.model.ModelsMap;
import org.openapitools.codegen.model.OperationsMap;
import org.openapitools.codegen.model.WebhooksMap;
import org.openapitools.codegen.serializer.SerializerUtils;
import org.openapitools.codegen.templating.MustacheEngineAdapter;
import org.openapitools.codegen.templating.mustache.BackSlashLambda;
import org.openapitools.codegen.templating.mustache.CamelCaseAndSanitizeLambda;
import org.openapitools.codegen.templating.mustache.DoubleQuoteLambda;
import org.openapitools.codegen.templating.mustache.ForwardSlashLambda;
import org.openapitools.codegen.templating.mustache.IndentedLambda;
import org.openapitools.codegen.templating.mustache.KebabCaseLambda;
import org.openapitools.codegen.templating.mustache.LowercaseLambda;
import org.openapitools.codegen.templating.mustache.SnakecaseLambda;
import org.openapitools.codegen.templating.mustache.TitlecaseLambda;
import org.openapitools.codegen.templating.mustache.UncamelizeLambda;
import org.openapitools.codegen.templating.mustache.UppercaseLambda;
import org.openapitools.codegen.utils.ModelUtils;
import org.openapitools.codegen.utils.OneOfImplementorAdditionalData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static io.micronaut.openapi.generator.Utils.DIVIDE_OPERATIONS_BY_CONTENT_TYPE;
import static org.openapitools.codegen.CodegenConstants.UNSUPPORTED_V310_SPEC_MSG;
import static org.openapitools.codegen.utils.CamelizeOption.LOWERCASE_FIRST_LETTER;
import static org.openapitools.codegen.utils.OnceLogger.once;
import static org.openapitools.codegen.utils.StringUtils.NAME_CACHE_EXPIRY_PROPERTY;
import static org.openapitools.codegen.utils.StringUtils.NAME_CACHE_SIZE_PROPERTY;
import static org.openapitools.codegen.utils.StringUtils.camelize;
import static org.openapitools.codegen.utils.StringUtils.escape;
import static org.openapitools.codegen.utils.StringUtils.underscore;
public class DefaultCodegen implements CodegenConfig {
private 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 final Cache sanitizedNameCache;
private static final String xSchemaTestExamplesKey = "x-schema-test-examples";
private static final String xSchemaTestExamplesRefPrefix = "#/components/x-schema-test-examples/";
protected static Schema falseSchema;
protected static Schema trueSchema = new Schema();
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.Object, 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 and SignatureAuth and AW4Signature are not yet 100% 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();
falseSchema = new Schema();
falseSchema.setNot(new Schema());
}
protected GeneratorMetadata generatorMetadata;
protected String inputSpec;
protected String outputFolder = "";
protected Set defaultIncludes;
protected Map typeMapping;
// instantiationTypes map from container types only: set, map, and array to the in language-type
protected Map instantiationTypes;
protected Set reservedWords;
protected Set languageSpecificPrimitives = new HashSet<>();
protected Set openapiGeneratorIgnoreList = new HashSet<>();
protected Map importMapping = new HashMap<>();
// a map to store the mapping between a schema and the new one
protected Map schemaMapping = new HashMap<>();
// a map to store the mapping between inline schema and the name provided by the user
protected Map inlineSchemaNameMapping = new HashMap<>();
// a map to store the inline schema naming conventions
protected Map inlineSchemaOption = new HashMap<>();
// a map to store the mapping between property name and the name provided by the user
protected Map nameMapping = new HashMap<>();
// a map to store the mapping between parameter name and the name provided by the user
protected Map parameterNameMapping = new HashMap<>();
// a map to store the mapping between model name and the name provided by the user
protected Map modelNameMapping = new HashMap<>();
// a map to store the mapping between enum name and the name provided by the user
protected Map enumNameMapping = new HashMap<>();
// a map to store the mapping between operation id name and the name provided by the user
protected Map operationIdNameMapping = new HashMap<>();
// a map to store the rules in OpenAPI Normalizer
protected Map openapiNormalizer = new HashMap<>();
protected String fileSuffix;
protected String modelNamePrefix = "";
protected String modelNameSuffix = "";
protected String apiNamePrefix = "";
protected String apiNameSuffix = "Api";
protected String testPackage = "";
protected String modelPackage = "";
protected String apiPackage = "";
protected String filesMetadataFilename = "FILES";
protected String versionMetadataFilename = "VERSION";
/*
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<>();
protected Map templateOutputDirs = 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;
protected String removeOperationIdPrefixDelimiter = "_";
protected int removeOperationIdPrefixCount = 1;
protected boolean skipOperationExample;
// sort operations by default
protected boolean skipSortingOperations = false;
protected static final Pattern XML_MIME_PATTERN = Pattern.compile("(?i)application/(.*)[+]?xml(;.*)?");
protected static final Pattern JSON_MIME_PATTERN = Pattern.compile("(?i)application/json(;.*)?");
protected static final Pattern JSON_VENDOR_MIME_PATTERN = Pattern.compile("(?i)application/vnd.(.*)+json(;.*)?");
private static final Pattern COMMON_PREFIX_ENUM_NAME = Pattern.compile("[a-zA-Z0-9]+\\z");
/**
* 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"
// Then translated back during JSON encoding and decoding
protected Map specialCharReplacements = new LinkedHashMap<>();
// When a model is an alias for a simple type
protected Map typeAliases = Collections.emptyMap();
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;
// If the server adds new enum cases, that are unknown by an old spec/client, the client will fail to parse the network response.
// With this option enabled, each enum will have a new case, 'unknown_default_open_api', so that when the server sends an enum case that is not known by the client/spec, they can safely fallback to this case.
protected boolean enumUnknownDefaultCase = false;
protected String enumUnknownDefaultCaseName = "unknown_default_open_api";
// make openapi available to all methods
protected OpenAPI openAPI;
// A cache to efficiently look up a Schema instance based on the return value of `toModelName()`.
private Map modelNameToSchemaCache;
// A cache to efficiently lookup schema `toModelName()` based on the schema Key
private final Map schemaKeyToModelNameCache = new HashMap<>();
protected boolean loadDeepObjectIntoItems = true;
// if true then baseTypes will be imported
protected boolean importBaseType = true;
// if true then container types will be imported
protected boolean importContainerType = true;
protected boolean addSuffixToDuplicateOperationNicknames = true;
// Whether to automatically hardcode params that are considered Constants by OpenAPI Spec
protected boolean autosetConstants = false;
@Override
public boolean getAddSuffixToDuplicateOperationNicknames() {
return addSuffixToDuplicateOperationNicknames;
}
@Override
public List cliOptions() {
return cliOptions;
}
/**
* add this instance to additionalProperties.
* This instance is used as parent context in Mustache.
* It means that Mustache uses the values found in this order:
* first from additionalProperties
* then from the getter in this instance
* then from the fields in this instance
*
*/
protected void useCodegenAsMustacheParentContext() {
additionalProperties.put(CodegenConstants.MUSTACHE_PARENT_CONTEXT, this);
}
@Override
public void processOpts() {
if (!additionalProperties.containsKey(CodegenConstants.MUSTACHE_PARENT_CONTEXT)) {
// by default empty parent context
additionalProperties.put(CodegenConstants.MUSTACHE_PARENT_CONTEXT, new Object());
}
convertPropertyToStringAndWriteBack(CodegenConstants.TEMPLATE_DIR, this::setTemplateDir);
convertPropertyToStringAndWriteBack(CodegenConstants.MODEL_PACKAGE, this::setModelPackage);
convertPropertyToStringAndWriteBack(CodegenConstants.API_PACKAGE, this::setApiPackage);
convertPropertyToBooleanAndWriteBack(CodegenConstants.HIDE_GENERATION_TIMESTAMP, this::setHideGenerationTimestamp);
// put the value back in additionalProperties for backward compatibility with generators not using yet convertPropertyToBooleanAndWriteBack
writePropertyBack(CodegenConstants.HIDE_GENERATION_TIMESTAMP, isHideGenerationTimestamp());
convertPropertyToBooleanAndWriteBack(CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG, this::setSortParamsByRequiredFlag);
convertPropertyToBooleanAndWriteBack(CodegenConstants.SORT_MODEL_PROPERTIES_BY_REQUIRED_FLAG, this::setSortModelPropertiesByRequiredFlag);
convertPropertyToBooleanAndWriteBack(CodegenConstants.PREPEND_FORM_OR_BODY_PARAMETERS, this::setPrependFormOrBodyParameters);
convertPropertyToBooleanAndWriteBack(CodegenConstants.ENSURE_UNIQUE_PARAMS, this::setEnsureUniqueParams);
convertPropertyToBooleanAndWriteBack(CodegenConstants.ALLOW_UNICODE_IDENTIFIERS, this::setAllowUnicodeIdentifiers);
convertPropertyToStringAndWriteBack(CodegenConstants.API_NAME_PREFIX, this::setApiNamePrefix);
convertPropertyToStringAndWriteBack(CodegenConstants.API_NAME_SUFFIX, this::setApiNameSuffix);
convertPropertyToStringAndWriteBack(CodegenConstants.MODEL_NAME_PREFIX, this::setModelNamePrefix);
convertPropertyToStringAndWriteBack(CodegenConstants.MODEL_NAME_SUFFIX, this::setModelNameSuffix);
convertPropertyToBooleanAndWriteBack(CodegenConstants.REMOVE_OPERATION_ID_PREFIX, this::setRemoveOperationIdPrefix);
convertPropertyToStringAndWriteBack(CodegenConstants.REMOVE_OPERATION_ID_PREFIX_DELIMITER, this::setRemoveOperationIdPrefixDelimiter);
convertPropertyToTypeAndWriteBack(CodegenConstants.REMOVE_OPERATION_ID_PREFIX_COUNT, Integer::parseInt, this::setRemoveOperationIdPrefixCount);
convertPropertyToBooleanAndWriteBack(CodegenConstants.SKIP_OPERATION_EXAMPLE, this::setSkipOperationExample);
convertPropertyToStringAndWriteBack(CodegenConstants.DOCEXTENSION, this::setDocExtension);
convertPropertyToBooleanAndWriteBack(CodegenConstants.ENABLE_POST_PROCESS_FILE, this::setEnablePostProcessFile);
convertPropertyToBooleanAndWriteBack(CodegenConstants.GENERATE_ALIAS_AS_MODEL, ModelUtils::setGenerateAliasAsModel);
convertPropertyToBooleanAndWriteBack(CodegenConstants.REMOVE_ENUM_VALUE_PREFIX, this::setRemoveEnumValuePrefix);
convertPropertyToBooleanAndWriteBack(CodegenConstants.LEGACY_DISCRIMINATOR_BEHAVIOR, this::setLegacyDiscriminatorBehavior);
convertPropertyToBooleanAndWriteBack(CodegenConstants.DISALLOW_ADDITIONAL_PROPERTIES_IF_NOT_PRESENT, this::setDisallowAdditionalPropertiesIfNotPresent);
convertPropertyToBooleanAndWriteBack(CodegenConstants.ENUM_UNKNOWN_DEFAULT_CASE, this::setEnumUnknownDefaultCase);
convertPropertyToBooleanAndWriteBack(CodegenConstants.AUTOSET_CONSTANTS, this::setAutosetConstants);
}
/***
* 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 with common lambdas
*/
protected ImmutableMap.Builder addMustacheLambdas() {
return new ImmutableMap.Builder()
.put("lowercase", new LowercaseLambda().generator(this))
.put("uppercase", new UppercaseLambda())
.put("snakecase", new SnakecaseLambda())
.put("titlecase", new TitlecaseLambda())
.put("kebabcase", new KebabCaseLambda())
.put("camelcase", new CamelCaseAndSanitizeLambda(true).generator(this))
.put("pascalcase", new CamelCaseAndSanitizeLambda(false).generator(this))
.put("uncamelize", new UncamelizeLambda())
.put("forwardslash", new ForwardSlashLambda())
.put("backslash", new BackSlashLambda())
.put("doublequote", new DoubleQuoteLambda())
.put("indented", new IndentedLambda())
.put("indented_8", new IndentedLambda(8, " ", false, false))
.put("indented_12", new IndentedLambda(12, " ", false, false))
.put("indented_16", new IndentedLambda(16, " ", false, false));
}
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
@Override
@SuppressWarnings("static-method")
public Map postProcessAllModels(Map objs) {
for (Entry entry : objs.entrySet()) {
CodegenModel model = ModelUtils.getModelByName(entry.getKey(), objs);
if (model == null) {
LOGGER.warn("Null model found in postProcessAllModels: {}", entry.getKey());
continue;
}
// add the model to the discriminator's mapping so templates have access to more than just the string to string mapping
if (model.discriminator != null && model.discriminator.getMappedModels() != null) {
for (MappedModel mappedModel : model.discriminator.getMappedModels()) {
CodegenModel mappedCodegenModel = ModelUtils.getModelByName(mappedModel.getModelName(), objs);
mappedModel.setModel(mappedCodegenModel);
}
}
for (CodegenProperty property : model.allVars) {
property.isNew = codegenPropertyIsNew(model, property);
}
for (CodegenProperty property : model.vars) {
property.isNew = codegenPropertyIsNew(model, property);
}
for (CodegenProperty property : model.readWriteVars) {
property.isNew = codegenPropertyIsNew(model, property);
}
for (CodegenProperty property : model.optionalVars) {
property.isNew = codegenPropertyIsNew(model, property);
}
for (CodegenProperty property : model.parentVars) {
property.isNew = codegenPropertyIsNew(model, property);
}
for (CodegenProperty property : model.requiredVars) {
property.isNew = codegenPropertyIsNew(model, property);
}
for (CodegenProperty property : model.readOnlyVars) {
property.isNew = codegenPropertyIsNew(model, property);
}
for (CodegenProperty property : model.nonNullableVars) {
property.isNew = codegenPropertyIsNew(model, property);
}
}
if (this.useOneOfInterfaces) {
// First, add newly created oneOf interfaces
for (CodegenModel cm : addOneOfInterfaces) {
ModelMap modelMapValue = new ModelMap(additionalProperties());
modelMapValue.setModel(cm);
List> importsValue = new ArrayList<>();
ModelsMap objsValue = new ModelsMap();
objsValue.setModels(Collections.singletonList(modelMapValue));
objsValue.put("package", modelPackage());
objsValue.setImports(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 (ModelsMap modelsAttrs : objs.values()) {
List> modelsImports = modelsAttrs.getImportsOrEmpty();
for (ModelMap mo : modelsAttrs.getModels()) {
CodegenModel cm = mo.getModel();
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 (Entry modelsEntry : objs.entrySet()) {
ModelsMap modelsAttrs = modelsEntry.getValue();
List> imports = modelsAttrs.getImports();
for (ModelMap implmo : modelsAttrs.getModels()) {
CodegenModel implcm = implmo.getModel();
String modelName = toModelName(implcm.name);
if (additionalDataMap.containsKey(modelName)) {
additionalDataMap.get(modelName).addToImplementor(this, implcm, imports, addOneOfInterfaceImports);
}
}
}
}
return objs;
}
/**
* A property is new if it is in a derived class and the derived property is different.
* This usually occurs when the data type is different.
* We can also consider discriminators as new because the derived class discriminator will have to be defined again
* to contain a new value. Doing so prevents having to include the discriminator in the constructor.
* @param model
* @param property
* @return
*/
private boolean codegenPropertyIsNew(CodegenModel model, CodegenProperty property) {
return model.parentModel == null
? false
: model.parentModel.allVars.stream().anyMatch(p ->
p.name.equals(property.name) &&
(p.dataType.equals(property.dataType) == false || p.datatypeWithEnum.equals(property.datatypeWithEnum) == false || p.isDiscriminator));
}
/**
* 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 LinkedHashMap<>();
for (Entry entry : objs.entrySet()) {
String modelName = toModelName(entry.getKey());
List models = entry.getValue().getModels();
for (ModelMap mo : models) {
CodegenModel cm = mo.getModel();
allModels.put(modelName, cm);
}
}
return allModels;
}
/**
* Loop through all models to update different flags (e.g. isSelfReference), children models, etc
* and update mapped models for import.
*
* @param objs Map of models
* @return maps of models with various updates
*/
@Override
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 (Entry allModelsEntry : allModels.entrySet()) {
String name = allModelsEntry.getKey();
CodegenModel cm = allModelsEntry.getValue();
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.schemaName);
if (parentSchema == null) {
LOGGER.warn("Failed to look up parent schema: {}", parent.schemaName);
parent = null;
} else {
if (parentSchema.getDiscriminator() == null) {
parent = allModels.get(parent.getParent());
} else {
parent = null;
}
}
}
}
// loop through properties of each model to detect self-reference
// and update mapped models for import
for (ModelsMap entry : objs.values()) {
for (ModelMap mo : entry.getModels()) {
CodegenModel cm = mo.getModel();
removeSelfReferenceImports(cm);
if (!this.getLegacyDiscriminatorBehavior()) {
cm.addDiscriminatorMappedModelsImports(true);
}
}
}
setCircularReferences(allModels);
return objs;
}
/**
* Removes importToRemove from the imports of objs, if present.
* This is useful to remove imports that are already present in operations-related template files, to avoid importing the same thing twice.
*
* @param objs imports will be removed from this objs' imports collection
* @param importToRemove the import statement to be removed
*/
protected void removeImport(OperationsMap objs, String importToRemove) {
List> imports = objs.getImports();
for (Iterator> itr = imports.iterator(); itr.hasNext(); ) {
String itrImport = itr.next().get("import");
if (itrImport.equals(importToRemove)) {
itr.remove();
}
}
}
/**
* Removes imports from the model that points to itself
* Marks a self referencing property, if detected
*
* @param model Self imports will be removed from this model.imports collection
*/
protected void removeSelfReferenceImports(CodegenModel model) {
for (CodegenProperty cp : model.allVars) {
// detect self import
if (cp.dataType.equalsIgnoreCase(model.classname) ||
(cp.isContainer && cp.items != null && cp.items.dataType.equalsIgnoreCase(model.classname))) {
model.imports.remove(model.classname); // remove self import
cp.isSelfReference = true;
}
}
}
public void setCircularReferences(Map models) {
// for allVars
final Map> allVarsDependencyMap = models.entrySet().stream()
.collect(Collectors.toMap(Entry::getKey, entry -> getModelDependencies(entry.getValue().getAllVars())));
models.keySet().forEach(name -> setCircularReferencesOnProperties(name, allVarsDependencyMap));
// for vars
final Map> varsDependencyMap = models.entrySet().stream()
.collect(Collectors.toMap(Entry::getKey, entry -> getModelDependencies(entry.getValue().getVars())));
models.keySet().forEach(name -> setCircularReferencesOnProperties(name, varsDependencyMap));
// for oneOf
final Map> oneOfDependencyMap = models.entrySet().stream()
.collect(Collectors.toMap(Entry::getKey, entry -> getModelDependencies(
(entry.getValue().getComposedSchemas() != null && entry.getValue().getComposedSchemas().getOneOf() != null)
? entry.getValue().getComposedSchemas().getOneOf() : new ArrayList())));
models.keySet().forEach(name -> setCircularReferencesOnProperties(name, oneOfDependencyMap));
}
private List getModelDependencies(List vars) {
return vars.stream()
.map(prop -> {
if (prop.isContainer) {
return prop.items.dataType == null ? null : prop;
}
return prop.dataType == null ? null : prop;
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
private void setCircularReferencesOnProperties(final String root,
final Map> dependencyMap) {
dependencyMap.getOrDefault(root, new ArrayList<>())
.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
@Override
@SuppressWarnings("static-method")
public ModelsMap postProcessModels(ModelsMap 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 ModelsMap postProcessModelsEnum(ModelsMap objs) {
for (ModelMap mo : objs.getModels()) {
CodegenModel cm = mo.getModel();
// 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);
postProcessEnumVars(enumVars);
// 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.nonNullableVars) {
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")
final Matcher matcher = COMMON_PREFIX_ENUM_NAME.matcher(prefix);
return matcher.replaceAll("");
} 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 default value in the language specified format
*
* @param property The codegen property to create the default for.
* @param value Enum variable name
* @return the default value for the enum
*/
public String toEnumDefaultValue(CodegenProperty property, String value) {
// Use the datatype with the value.
return toEnumDefaultValue(value, property.datatypeWithEnum);
}
/**
* 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) || "boolean".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.*")) {
var = "_" + var;
}
if (reservedWords.contains(var)) {
return escapeReservedWord(var);
}
return var;
}
public boolean specVersionGreaterThanOrEqualTo310(OpenAPI openAPI) {
String originalSpecVersion;
String xOriginalSwaggerVersion = "x-original-swagger-version";
if (openAPI.getExtensions() != null && !openAPI.getExtensions().isEmpty() && openAPI.getExtensions().containsValue(xOriginalSwaggerVersion)) {
originalSpecVersion = (String) openAPI.getExtensions().get(xOriginalSwaggerVersion);
} else {
originalSpecVersion = openAPI.getOpenapi();
}
Integer specMajorVersion = Integer.parseInt(originalSpecVersion.substring(0, 1));
Integer specMinorVersion = Integer.parseInt(originalSpecVersion.substring(2, 3));
return specMajorVersion == 3 && specMinorVersion >= 1;
}
/**
* Set the OpenAPI document.
* This method is invoked when the input OpenAPI document has been parsed and validated.
*/
@Override
public void setOpenAPI(OpenAPI openAPI) {
if (specVersionGreaterThanOrEqualTo310(openAPI)) {
LOGGER.warn(UNSUPPORTED_V310_SPEC_MSG);
}
this.openAPI = openAPI;
// Set global settings such that helper functions in ModelUtils can lookup the value
// of the CLI option.
ModelUtils.setDisallowAdditionalPropertiesIfNotPresent(getDisallowAdditionalPropertiesIfNotPresent());
// Multiple operations rely on proper type aliases, so we should always update them
typeAliases = getAllAliases(ModelUtils.getSchemas(openAPI));
}
// override with any message to be shown right before the process finishes
@Override
@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
@Override
@SuppressWarnings("static-method")
public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List allModels) {
return objs;
}
// override with any special post-processing
@Override
@SuppressWarnings("static-method")
public WebhooksMap postProcessWebhooksWithModels(WebhooksMap objs, List allModels) {
return objs;
}
// override with any special post-processing
@Override
@SuppressWarnings("static-method")
public Map postProcessSupportingFileData(Map objs) {
return objs;
}
// override to post-process any model properties
@Override
@SuppressWarnings("unused")
public void postProcessModelProperty(CodegenModel model, CodegenProperty property) {
}
// override to post-process any response
@Override
@SuppressWarnings("unused")
public void postProcessResponseWithProperty(CodegenResponse response, CodegenProperty property) {
}
// override to post-process any parameters
@Override
@SuppressWarnings("unused")
public void postProcessParameter(CodegenParameter parameter) {
}
//override with any special handling of the entire OpenAPI spec document
@Override
@SuppressWarnings("unused")
public void preprocessOpenAPI(OpenAPI openAPI) {
var divideOperationsByContentType = Boolean.parseBoolean(GlobalSettings.getProperty(DIVIDE_OPERATIONS_BY_CONTENT_TYPE, "false"));
if (divideOperationsByContentType && openAPI.getPaths() != null && !openAPI.getPaths().isEmpty()) {
for (Entry entry : openAPI.getPaths().entrySet()) {
String pathStr = entry.getKey();
PathItem path = entry.getValue();
List getOps = divideOperationsByContentType(pathStr, PathItem.HttpMethod.GET, path.getGet());
if (!getOps.isEmpty()) {
path.addExtension("x-get", getOps);
}
List putOps = divideOperationsByContentType(pathStr, PathItem.HttpMethod.PUT, path.getPut());
if (!putOps.isEmpty()) {
path.addExtension("x-put", putOps);
}
List postOps = divideOperationsByContentType(pathStr, PathItem.HttpMethod.POST, path.getPost());
if (!postOps.isEmpty()) {
path.addExtension("x-post", postOps);
}
List deleteOps = divideOperationsByContentType(pathStr, PathItem.HttpMethod.DELETE, path.getDelete());
if (!deleteOps.isEmpty()) {
path.addExtension("x-delete", deleteOps);
}
List optionsOps = divideOperationsByContentType(pathStr, PathItem.HttpMethod.OPTIONS, path.getOptions());
if (!optionsOps.isEmpty()) {
path.addExtension("x-options", optionsOps);
}
List headOps = divideOperationsByContentType(pathStr, PathItem.HttpMethod.HEAD, path.getHead());
if (!headOps.isEmpty()) {
path.addExtension("x-head", headOps);
}
List patchOps = divideOperationsByContentType(pathStr, PathItem.HttpMethod.PATCH, path.getPatch());
if (!patchOps.isEmpty()) {
path.addExtension("x-patch", patchOps);
}
List traceOps = divideOperationsByContentType(pathStr, PathItem.HttpMethod.TRACE, path.getTrace());
if (!traceOps.isEmpty()) {
path.addExtension("x-trace", traceOps);
}
}
}
if (useOneOfInterfaces && openAPI.getComponents() != null) {
// 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 (Entry e : pathItems.entrySet()) {
for (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
if (op.getValue().getResponses() != null) {
for (Entry ar : op.getValue().getResponses().entrySet()) {
ApiResponse a = ModelUtils.getReferencedApiResponse(openAPI, ar.getValue());
Schema responseSchema = unaliasSchema(ModelUtils.getSchemaFromResponse(openAPI, 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 (Entry e : schemas.entrySet()) {
Schema s = e.getValue();
Map props = s.getProperties();
if (props == null) {
props = new HashMap<>();
}
for (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 (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(s, nOneOf);
addOneOfInterfaceModel(s, nOneOf);
} else {
// else this is a component schema, so we will just use that as the oneOf interface model
addOneOfNameExtension(s, n);
}
} else if (ModelUtils.isArraySchema(s)) {
Schema items = ModelUtils.getSchemaItems(s);
if (ModelUtils.isComposedSchema(items)) {
addOneOfNameExtension(items, nOneOf);
addOneOfInterfaceModel(items, nOneOf);
}
} else if (ModelUtils.isMapSchema(s)) {
Schema addProps = ModelUtils.getAdditionalProperties(s);
if (addProps != null && ModelUtils.isComposedSchema(addProps)) {
addOneOfNameExtension(addProps, nOneOf);
addOneOfInterfaceModel(addProps, nOneOf);
}
}
}
}
}
private List divideOperationsByContentType(String path, PathItem.HttpMethod httpMethod, Operation op) {
if (op == null) {
return Collections.emptyList();
}
var additionalOps = new ArrayList();
RequestBody body = op.getRequestBody();
if (body == null || body.getContent() == null) {
return Collections.emptyList();
}
Content content = body.getContent();
if (content.size() <= 1) {
return Collections.emptyList();
}
var firstEntry = content.entrySet().iterator().next();
var mediaTypesToRemove = new ArrayList();
for (var entry : content.entrySet()) {
if (mediaTypesToRemove.contains(entry.getKey()) || entry.getKey().equals(firstEntry.getKey()) || entry.getValue().equals(firstEntry.getValue())) {
continue;
}
var foundSameOpSignature = false;
for (var additionalOp : additionalOps) {
RequestBody additionalBody = additionalOp.getRequestBody();
if (additionalBody == null || additionalBody.getContent() == null) {
return Collections.emptyList();
}
for (var addContentEntry : additionalBody.getContent().entrySet()) {
if (addContentEntry.getValue().equals(entry.getValue())) {
foundSameOpSignature = true;
break;
}
}
if (foundSameOpSignature) {
additionalBody.getContent().put(entry.getKey(), entry.getValue());
break;
}
}
mediaTypesToRemove.add(entry.getKey());
if (foundSameOpSignature) {
continue;
}
additionalOps.add(new Operation()
.deprecated(op.getDeprecated())
.callbacks(op.getCallbacks())
.description(op.getDescription())
.extensions(op.getExtensions())
.externalDocs(op.getExternalDocs())
.operationId(getOrGenerateOperationId(op, path, httpMethod.name()))
.parameters(op.getParameters())
.responses(op.getResponses())
.security(op.getSecurity())
.servers(op.getServers())
.summary(op.getSummary())
.tags(op.getTags())
.requestBody(new RequestBody()
.description(body.getDescription())
.extensions(body.getExtensions())
.content(new Content()
.addMediaType(entry.getKey(), entry.getValue()))
)
);
}
if (!mediaTypesToRemove.isEmpty()) {
content.entrySet().removeIf(stringMediaTypeEntry -> mediaTypesToRemove.contains(stringMediaTypeEntry.getKey()));
}
return additionalOps;
}
// override with any special handling of the entire OpenAPI spec document
@Override
@SuppressWarnings("unused")
public void processOpenAPI(OpenAPI openAPI) {
}
// override with any special handling of the JMustache compiler
@Override
@SuppressWarnings("unused")
public Compiler processCompiler(Compiler compiler) {
return compiler;
}
// override with any special handling for the templating engine
@Override
@SuppressWarnings("unused")
public TemplatingEngineAdapter processTemplatingEngine(TemplatingEngineAdapter templatingEngine) {
return templatingEngine;
}
// override with any special text escaping logic
@Override
@SuppressWarnings("static-method")
public String escapeText(String input) {
if (input == null) {
return input;
}
// remove \t, \n, \r
// replace \ with \\
// replace " with \"
// outer 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
*/
@Override
public String escapeTextWhileAllowingNewLines(String input) {
if (input == null) {
return input;
}
// remove \t
// replace \ with \\
// replace " with \"
// outer 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
@Override
@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
*/
@Override
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
*/
@Override
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("\"", "\\\"");
}
@Override
public Set defaultIncludes() {
return defaultIncludes;
}
@Override
public Map typeMapping() {
return typeMapping;
}
@Override
public Map instantiationTypes() {
return instantiationTypes;
}
@Override
public Set reservedWords() {
return reservedWords;
}
@Override
public Set languageSpecificPrimitives() {
return languageSpecificPrimitives;
}
@Override
public Set openapiGeneratorIgnoreList() {
return openapiGeneratorIgnoreList;
}
@Override
public Map importMapping() {
return importMapping;
}
@Override
public Map schemaMapping() {
return schemaMapping;
}
@Override
public Map inlineSchemaNameMapping() {
return inlineSchemaNameMapping;
}
@Override
public Map inlineSchemaOption() {
return inlineSchemaOption;
}
@Override
public Map nameMapping() {
return nameMapping;
}
@Override
public Map parameterNameMapping() {
return parameterNameMapping;
}
@Override
public Map modelNameMapping() {
return modelNameMapping;
}
@Override
public Map enumNameMapping() {
return enumNameMapping;
}
@Override
public Map operationIdNameMapping() {
return operationIdNameMapping;
}
@Override
public Map openapiNormalizer() {
return openapiNormalizer;
}
@Override
public String testPackage() {
return testPackage;
}
@Override
public String modelPackage() {
return modelPackage;
}
@Override
public String apiPackage() {
return apiPackage;
}
@Override
public String fileSuffix() {
return fileSuffix;
}
@Override
public String templateDir() {
return templateDir;
}
public void setTemplateDir(String templateDir) {
this.templateDir = templateDir;
}
@Override
public String embeddedTemplateDir() {
if (embeddedTemplateDir != null) {
return embeddedTemplateDir;
} else {
return templateDir;
}
}
@Override
public Map apiDocTemplateFiles() {
return apiDocTemplateFiles;
}
@Override
public Map modelDocTemplateFiles() {
return modelDocTemplateFiles;
}
@Override
public Map reservedWordsMappings() {
return reservedWordsMappings;
}
@Override
public Map apiTestTemplateFiles() {
return apiTestTemplateFiles;
}
@Override
public Map modelTestTemplateFiles() {
return modelTestTemplateFiles;
}
@Override
public Map apiTemplateFiles() {
return apiTemplateFiles;
}
@Override
public Map modelTemplateFiles() {
return modelTemplateFiles;
}
@Override
public String apiFileFolder() {
return outputFolder + File.separator + apiPackage().replace('.', File.separatorChar);
}
@Override
public String modelFileFolder() {
return outputFolder + File.separator + modelPackage().replace('.', File.separatorChar);
}
@Override
public String apiTestFileFolder() {
return outputFolder + File.separator + testPackage().replace('.', File.separatorChar);
}
@Override
public String modelTestFileFolder() {
return outputFolder + File.separator + testPackage().replace('.', File.separatorChar);
}
@Override
public String apiDocFileFolder() {
return outputFolder;
}
@Override
public String modelDocFileFolder() {
return outputFolder;
}
@Override
public Map additionalProperties() {
return additionalProperties;
}
@Override
public Map serverVariableOverrides() {
return serverVariables;
}
@Override
public Map vendorExtensions() {
return vendorExtensions;
}
@Override
public Map templateOutputDirs() {
return templateOutputDirs;
}
@Override
public List supportingFiles() {
return supportingFiles;
}
@Override
public String outputFolder() {
return outputFolder;
}
@Override
public void setOutputDir(String dir) {
this.outputFolder = dir;
}
@Override
public String getOutputDir() {
return outputFolder();
}
@Override
public String getInputSpec() {
return inputSpec;
}
@Override
public void setInputSpec(String inputSpec) {
this.inputSpec = inputSpec;
}
@Override
public String getFilesMetadataFilename() {
return filesMetadataFilename;
}
@Override
public String getVersionMetadataFilename() {
return versionMetadataFilename;
}
public Boolean getLegacyDiscriminatorBehavior() {
return legacyDiscriminatorBehavior;
}
public Boolean getDisallowAdditionalPropertiesIfNotPresent() {
return disallowAdditionalPropertiesIfNotPresent;
}
public Boolean getEnumUnknownDefaultCase() {
return enumUnknownDefaultCase;
}
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
*
* @param name the file name of the Api
* @return the file name of the Api
*/
@Override
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
*/
@Override
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
*/
@Override
public String toApiTestFilename(String name) {
return toApiName(name) + "Test";
}
/**
* Return the variable name in the Api
*
* @param name the variable name of the Api
* @return the snake-cased variable name
*/
@Override
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
*/
@Override
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
*/
@Override
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
*/
@Override
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) {
// obtain the name from nameMapping directly if provided
if (nameMapping.containsKey(name)) {
return nameMapping.get(name);
}
if (reservedWords.contains(name)) {
return escapeReservedWord(name);
} else if (name.chars().anyMatch(character -> specialCharReplacements.containsKey(String.valueOf((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
*/
@Override
public String toParamName(String name) {
// obtain the name from parameterNameMapping directly if provided
if (parameterNameMapping.containsKey(name)) {
return parameterNameMapping.get(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(String.valueOf((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)
*/
@Override
@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
*/
@Override
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.
*/
@Override
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
*/
@Override
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("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("integer", "Integer");
typeMapping.put("UnsignedInteger", "Integer");
typeMapping.put("UnsignedLong", "Long");
typeMapping.put("char", "String");
typeMapping.put("object", "Object");
// FIXME: java specific type should be in Java Based Abstract Impl's
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);
CliOption enumUnknownDefaultCaseOpt = CliOption.newBoolean(
CodegenConstants.ENUM_UNKNOWN_DEFAULT_CASE,
CodegenConstants.ENUM_UNKNOWN_DEFAULT_CASE_DESC).defaultValue(Boolean.FALSE.toString());
Map enumUnknownDefaultCaseOpts = new HashMap<>();
enumUnknownDefaultCaseOpts.put("false",
"No changes to the enum's are made, this is the default option.");
enumUnknownDefaultCaseOpts.put("true",
"With this option enabled, each enum will have a new case, 'unknown_default_open_api', so that when the enum case sent by the server is not known by the client/spec, can safely be decoded to this case.");
enumUnknownDefaultCaseOpt.setEnum(enumUnknownDefaultCaseOpts);
cliOptions.add(enumUnknownDefaultCaseOpt);
this.setEnumUnknownDefaultCase(false);
// 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(";", "Semicolon");
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("<>", "Not_Equal");
specialCharReplacements.put("~=", "Tilde_Equal");
specialCharReplacements.put("==", "Double_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
*/
@Override
@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 '{}' style not support: {}", param.getName(), qp.getStyle());
}
} else {
paramPart.append(param.getName());
}
paramPart.append("}");
if (!param.getRequired()) {
paramPart.append("]");
}
sb.append(paramPart);
}
}
}
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 = ModelUtils.getAdditionalProperties(schema);
String inner = getSchemaType(additionalProperties);
String mapInstantion = instantiationTypes.get("map");
if (mapInstantion != null) {
return mapInstantion + "";
}
return inner;
} else if (ModelUtils.isArraySchema(schema)) {
String inner = getSchemaType(ModelUtils.getSchemaItems(schema));
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 examples of the parameter.
*
* @param codegenParameter Codegen parameter
* @param parameter Parameter
*/
public void setParameterExamples(CodegenParameter codegenParameter, Parameter parameter) {
if (parameter.getExamples() != null && !parameter.getExamples().isEmpty()) {
codegenParameter.examples = parameter.getExamples();
}
}
/**
* 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).debug("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, style, and explode 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 setParameterEncodingValues(CodegenParameter codegenParameter, MediaType mediaType) {
if (mediaType != null && mediaType.getEncoding() != null) {
Encoding encoding = mediaType.getEncoding().get(codegenParameter.baseName);
if (encoding != null) {
boolean styleGiven = true;
Encoding.StyleEnum style = encoding.getStyle();
if (style == null || style == Encoding.StyleEnum.FORM) {
// (Unfortunately, swagger-parser-v3 will always provide 'form'
// when style is not specified, so we can't detect that)
style = Encoding.StyleEnum.FORM;
styleGiven = false;
}
boolean explodeGiven = true;
Boolean explode = encoding.getExplode();
if (explode == null) {
explode = style == Encoding.StyleEnum.FORM; // Default to True when form, False otherwise
explodeGiven = false;
}
if (!styleGiven && !explodeGiven) {
// Ignore contentType if style or explode are specified.
codegenParameter.contentType = encoding.getContentType();
}
codegenParameter.style = style.toString();
codegenParameter.isDeepObject = Encoding.StyleEnum.DEEP_OBJECT == style;
if (codegenParameter.isContainer) {
codegenParameter.isExplode = explode;
String collectionFormat = getCollectionFormat(codegenParameter);
codegenParameter.collectionFormat = StringUtils.isEmpty(collectionFormat) ? "csv" : collectionFormat;
codegenParameter.isCollectionFormatMulti = "multi".equals(collectionFormat);
} else {
codegenParameter.isExplode = false;
codegenParameter.collectionFormat = null;
codegenParameter.isCollectionFormatMulti = false;
}
} else {
LOGGER.debug("encoding not specified for {}", codegenParameter.baseName);
}
}
}
/**
* Return the example value of the property
*
* This method should be overridden in the generator to meet its requirement.
*
* @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 "null";
}
/**
* Return the default value of the property
*
* This method should be overridden in the generator to meet its requirement.
* 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 "null";
}
/**
* Return the default value of the parameter
*
* Return null if you do NOT want a default value.
* Any non-null value will cause {{#defaultValue} check to pass.
*
* @param schema Parameter schema
* @return string presentation of the default value of the parameter
*/
public String toDefaultParameterValue(Schema> schema) {
// by default works as original method to be backward compatible
return toDefaultValue(schema);
}
/**
* Return the default value of the parameter
*
* Return null if you do NOT want a default value.
* Any non-null value will cause {{#defaultValue} check to pass.
*
* @param codegenProperty Codegen Property
* @param schema Parameter schema
* @return string presentation of the default value of the parameter
*/
public String toDefaultParameterValue(CodegenProperty codegenProperty, Schema> schema) {
// by default works as original method to be backward compatible
return toDefaultParameterValue(schema);
}
/**
* 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 + ";";
}
/**
* 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
* @param codegenProperty Codegen property
* @return string presentation of the default value of the property
*/
public String toDefaultValue(CodegenProperty codegenProperty, Schema schema) {
// use toDefaultValue(schema) if generator has not overriden this method
return toDefaultValue(schema);
}
/**
* 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 (ModelUtils.isComposedSchema(schema)) { // composed schema
// Get the interfaces, i.e. the set of elements under 'allOf', 'anyOf' or 'oneOf'.
List schemas = ModelUtils.getInterfaces(schema);
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 (schema.getAllOf() != null) {
return toAllOfName(names, schema);
} else if (schema.getAnyOf() != null) { // anyOf
return toAnyOfName(names, schema);
} else if (schema.getOneOf() != null) { // oneOf
return toOneOfName(names, schema);
}
}
return getSingleSchemaType(schema);
}
protected Schema> getSchemaAdditionalProperties(Schema schema) {
Schema> inner = ModelUtils.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, Schema composedSchema) {
Map exts = composedSchema.getExtensions();
if (exts != null && exts.containsKey("x-all-of-name")) {
return (String) exts.get("x-all-of-name");
}
if (names.isEmpty()) {
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.debug("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, Schema 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, Schema 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) + ">";
}
@Override
public Schema unaliasSchema(Schema schema) {
return ModelUtils.unaliasSchema(this.openAPI, schema, schemaMapping);
}
/**
* 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);
if (ModelUtils.isRefToSchemaWithProperties(unaliasSchema.get$ref())) {
// ref to schema's properties, e.g. #/components/schemas/Pet/properties/category
Schema refSchema = ModelUtils.getReferencedSchema(openAPI, unaliasSchema);
if (refSchema != null) {
return getSingleSchemaType(refSchema);
}
}
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 (schemaMapping.containsKey(schemaName)) {
return schemaName;
}
return getAlias(schemaName);
} else {
LOGGER.warn("Error obtaining the datatype from ref: {}. Default to 'object'", unaliasSchema.get$ref());
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 (typeMapping.containsKey(ModelUtils.getType(schema) + "+" + schema.getFormat())) {
// allows custom type_format mapping.
// use {type}+{format}
return typeMapping.get(ModelUtils.getType(schema) + "+" + schema.getFormat());
} 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.isUnsignedLongSchema(schema)) {
return "UnsignedLong";
} else if (ModelUtils.isUnsignedIntegerSchema(schema)) {
return "UnsignedInteger";
} else if (ModelUtils.isLongSchema(schema)) {
return "long";
} else if (ModelUtils.isShortSchema(schema)) {// int32
return "integer";
} else {
return ModelUtils.getType(schema); // 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 (ModelUtils.isFreeFormObject(schema, openAPI)) {
// 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 (ModelUtils.isAnyType(schema)) {
return "AnyType";
} else if (StringUtils.isNotEmpty(ModelUtils.getType(schema))) {
if (!schemaMapping.containsKey(ModelUtils.getType(schema))) {
LOGGER.warn("Unknown type found in the schema: {}. To map it, please use the schema mapping option (e.g. --schema-mappings in CLI)", ModelUtils.getType(schema));
}
return ModelUtils.getType(schema);
}
// 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
*/
@Override
@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
*/
@Override
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
*/
@Override
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
*/
@Override
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
*/
@Override
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
*/
@Override
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 overridden 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
*/
@Override
public String toModelName(final String name) {
// obtain the name from modelNameMapping directly if provided
if (modelNameMapping.containsKey(name)) {
return modelNameMapping.get(name);
}
if (schemaKeyToModelNameCache.containsKey(name)) {
return schemaKeyToModelNameCache.get(name);
}
String camelizedName = camelize(modelNamePrefix + "_" + name + "_" + modelNameSuffix);
schemaKeyToModelNameCache.put(name, camelizedName);
return camelizedName;
}
private static class NamedSchema {
private NamedSchema(String name, Schema s, boolean required, boolean schemaIsFromAdditionalProperties) {
this.name = name;
this.schema = s;
this.required = required;
this.schemaIsFromAdditionalProperties = schemaIsFromAdditionalProperties;
}
private String name;
private Schema schema;
private boolean required;
private boolean schemaIsFromAdditionalProperties;
@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(required, that.required) &&
Objects.equals(name, that.name) &&
Objects.equals(schema, that.schema) &&
Objects.equals(schemaIsFromAdditionalProperties, that.schemaIsFromAdditionalProperties);
}
@Override
public int hashCode() {
return Objects.hash(name, schema, required, schemaIsFromAdditionalProperties);
}
}
Map schemaCodegenPropertyCache = new HashMap<>();
protected void updateModelForComposedSchema(CodegenModel m, Schema schema, Map allDefinitions) {
final Schema composed = 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 discriminator, 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 (Object innerSchema : composed.getAllOf()) { // TODO need to work with anyOf, oneOf as well
if (m.discriminator == null && ((Schema) innerSchema).getDiscriminator() != null) {
LOGGER.debug("discriminator is set to null (not correctly set earlier): {}", m.name);
m.setDiscriminator(createDiscriminator(m.name, (Schema) innerSchema));
modelDiscriminators++;
}
if (((Schema) innerSchema).getXml() != null) {
m.xmlPrefix = ((Schema) innerSchema).getXml().getPrefix();
m.xmlNamespace = ((Schema) innerSchema).getXml().getNamespace();
m.xmlName = ((Schema) innerSchema).getXml().getName();
}
if (modelDiscriminators > 1) {
LOGGER.debug("Allof composed schema is inheriting >1 discriminator. Only use one discriminator: {}", composed);
}
if (modelImplCnt++ > 1) {
LOGGER.debug("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);
if (StringUtils.isBlank(interfaceSchema.get$ref())) {
// primitive type
String languageType = getTypeDeclaration(interfaceSchema);
CodegenProperty interfaceProperty = fromProperty(languageType, interfaceSchema, false);
if (ModelUtils.isArraySchema(interfaceSchema) || ModelUtils.isMapSchema(interfaceSchema)) {
while (interfaceProperty != null) {
addImport(m, interfaceProperty.complexType);
interfaceProperty = interfaceProperty.items;
}
}
if (composed.getAnyOf() != null) {
if (m.anyOf.contains(languageType)) {
LOGGER.debug("{} (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.debug("{} (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);
CodegenProperty interfaceProperty = fromProperty(modelName, interfaceSchema, false);
m.interfaces.add(modelName);
addImport(composed, refSchema, m, modelName);
if (allDefinitions != null && refSchema != null) {
if (allParents.contains(ref) && supportsMultipleInheritance) {
// multiple inheritance
addProperties(allProperties, allRequired, refSchema, new HashSet<>());
} else if (parentName != null && parentName.equals(ref) && supportsInheritance) {
// single inheritance
addProperties(allProperties, allRequired, refSchema, new HashSet<>());
} else {
// composition
Map newProperties = new LinkedHashMap<>();
addProperties(newProperties, required, refSchema, new HashSet<>());
mergeProperties(properties, newProperties);
addProperties(allProperties, allRequired, refSchema, new HashSet<>());
}
}
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, new HashSet<>());
// includes child's properties (all, required) in allProperties, allRequired
addProperties(allProperties, allRequired, component, new HashSet<>());
}
// in 7.0.0 release, we comment out below to allow more than 1 child schemas in allOf
//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
}
/**
* Combines all previously-detected type entries for a schema with newly-discovered ones, to ensure
* that schema for items like enum include all possible values.
*/
private void mergeProperties(Map existingProperties, Map newProperties) {
// https://github.com/OpenAPITools/openapi-generator/issues/12545
if (null != existingProperties && null != newProperties) {
Schema existingType = existingProperties.get("type");
Schema newType = newProperties.get("type");
newProperties.forEach((key, value) ->
existingProperties.put(key, ModelUtils.cloneSchema(value, specVersionGreaterThanOrEqualTo310(openAPI)))
);
if (null != existingType && null != newType && null != newType.getEnum() && !newType.getEnum().isEmpty()) {
for (Object e : newType.getEnum()) {
// ensure all interface enum types are added to schema
if (null != existingType.getEnum() && !existingType.getEnum().contains(e)) {
existingType.addEnumItemObject(e);
}
}
existingProperties.put("type", existingType);
}
}
}
protected void updateModelForObject(CodegenModel m, Schema schema) {
if (schema.getProperties() != null || schema.getRequired() != null && !(ModelUtils.isComposedSchema(schema))) {
// passing null to allProperties and allRequired as there's no parent
addVars(m, unaliasPropertySchema(schema.getProperties()), schema.getRequired(), null, null);
}
if (ModelUtils.isMapSchema(schema)) {
// an object or anyType composed schema that has additionalProperties set
addAdditionPropertiesToCodeGenModel(m, schema);
} else if (ModelUtils.isFreeFormObject(schema, openAPI)) {
// non-composed object type with no properties + additionalProperties
// additionalProperties must be null, ObjectSchema, or empty Schema
addAdditionPropertiesToCodeGenModel(m, schema);
}
// process 'additionalProperties'
setAddProps(schema, m);
addRequiredVarsMap(schema, m);
}
protected void updateModelForAnyType(CodegenModel m, Schema 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'", m.name);
}
// m.isNullable = true;
if (ModelUtils.isMapSchema(schema)) {
// an object or anyType composed schema that has additionalProperties set
addAdditionPropertiesToCodeGenModel(m, schema);
m.isMap = true;
}
if (schema.getProperties() != null || schema.getRequired() != null && !(ModelUtils.isComposedSchema(schema))) {
// passing null to allProperties and allRequired as there's no parent
addVars(m, unaliasPropertySchema(schema.getProperties()), schema.getRequired(), null, null);
}
// process 'additionalProperties'
setAddProps(schema, m);
addRequiredVarsMap(schema, m);
}
protected String toTestCaseName(String specTestCaseName) {
return specTestCaseName;
}
/**
* A method that allows generators to pre-process test example payloads
* This can be useful if one needs to change how values like null in string are represented
*
* @param data the test data payload
* @return the updated test data payload
*/
protected Object processTestExampleData(Object data) {
return data;
}
/**
* Processes any test cases if they exist in the components.x-test-examples vendor extensions
* If they exist then cast them to java class instances and return them back in a map
*
* @param refToTestCases the component schema name that the test cases are for
*/
private HashMap extractSchemaTestCases(String refToTestCases) {
// schemaName to a map of test case name to test case
HashMap vendorExtensions = (HashMap) openAPI.getComponents().getExtensions();
if (vendorExtensions == null || !vendorExtensions.containsKey(xSchemaTestExamplesKey)) {
return null;
}
if (!refToTestCases.startsWith(xSchemaTestExamplesRefPrefix)) {
return null;
}
String schemaName = refToTestCases.substring(xSchemaTestExamplesRefPrefix.length());
HashMap schemaTestCases = new HashMap<>();
LinkedHashMap schemaNameToTestCases = (LinkedHashMap) vendorExtensions.get(xSchemaTestExamplesKey);
if (!schemaNameToTestCases.containsKey(schemaName)) {
return null;
}
LinkedHashMap> testNameToTesCase = (LinkedHashMap>) schemaNameToTestCases.get(schemaName);
for (Entry> entry : testNameToTesCase.entrySet()) {
LinkedHashMap testExample = (LinkedHashMap) entry.getValue();
String nameInSnakeCase = toTestCaseName(entry.getKey());
Object data = processTestExampleData(testExample.get("data"));
SchemaTestCase testCase = new SchemaTestCase(
(String) testExample.getOrDefault("description", ""),
new ObjectWithTypeBooleans(data),
(boolean) testExample.get("valid")
);
schemaTestCases.put(nameInSnakeCase, testCase);
}
return schemaTestCases;
}
/**
* Sets the booleans that define the model's type
*
* @param model the model to update
* @param schema the model's schema
*/
protected void updateModelForString(CodegenModel model, Schema schema) {
// NOTE: String schemas as CodegenModel is a rare use case and may be removed at a later date.
if (ModelUtils.isDateTimeSchema(schema)) {
// NOTE: DateTime schemas as CodegenModel is a rare use case and may be removed at a later date.
model.setIsString(false); // for backward compatibility with 2.x
model.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.
model.setIsString(false); // for backward compatibility with 2.x
model.isDate = Boolean.TRUE;
} else if (ModelUtils.isUUIDSchema(schema)) {
// NOTE: UUID schemas as CodegenModel is a rare use case and may be removed at a later date.
model.setIsString(false);
model.setIsUuid(true);
} else if (ModelUtils.isURISchema(schema)) {
model.setIsString(false);
model.setIsUri(true);
}
}
protected void updateModelForNumber(CodegenModel model, Schema schema) {
// NOTE: Number schemas as CodegenModel is a rare use case and may be removed at a later date.
model.isNumeric = Boolean.TRUE;
if (ModelUtils.isFloatSchema(schema)) { // float
model.isFloat = Boolean.TRUE;
} else if (ModelUtils.isDoubleSchema(schema)) { // double
model.isDouble = Boolean.TRUE;
}
}
protected void updateModelForInteger(CodegenModel model, Schema schema) {
// NOTE: Integral schemas as CodegenModel is a rare use case and may be removed at a later date.
model.isNumeric = Boolean.TRUE;
if (ModelUtils.isLongSchema(schema)) { // int64/long format
model.isLong = Boolean.TRUE;
} else {
model.isInteger = Boolean.TRUE; // older use case, int32 and unbounded int
if (ModelUtils.isShortSchema(schema)) { // int32
model.setIsShort(Boolean.TRUE);
}
}
}
/**
* Convert OAS Model object to Codegen Model object.
*
* @param name the name of the model
* @param schema OAS Model object
* @return Codegen Model object
*/
@Override
public CodegenModel fromModel(String name, Schema schema) {
Map allDefinitions = ModelUtils.getSchemas(this.openAPI);
CodegenModel m = CodegenModelFactory.newInstance(CodegenModelType.MODEL);
if (schema.equals(trueSchema)) {
m.setIsBooleanSchemaTrue(true);
} else if (schema.equals(falseSchema)) {
m.setIsBooleanSchemaFalse(true);
}
// unalias schema
schema = unaliasSchema(schema);
if (schema == null) {
LOGGER.warn("Schema {} not found", name);
return null;
}
ModelUtils.syncValidationProperties(schema, m);
if (openAPI != null) {
HashMap schemaTestCases = extractSchemaTestCases(xSchemaTestExamplesRefPrefix + name);
m.testCases = schemaTestCases;
}
if (reservedWords.contains(name)) {
m.name = escapeReservedWord(name);
} else {
m.name = name;
}
m.schemaName = name; // original schema 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.setDiscriminator(createDiscriminator(name, schema));
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 (!ModelUtils.isAnyType(schema) && !ModelUtils.isTypeObjectSchema(schema) && !ModelUtils.isArraySchema(schema) && schema.get$ref() == null && schema.getEnum() != null && !schema.getEnum().isEmpty()) {
// TODO remove the anyType check here in the future ANyType models can have enums defined
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.isArraySchema(schema)) {
m.dataType = getSchemaType(schema);
}
if (!ModelUtils.isAnyType(schema) && Boolean.TRUE.equals(schema.getNullable())) {
m.isNullable = Boolean.TRUE;
}
m.setTypeProperties(schema, openAPI);
m.setFormat(schema.getFormat());
m.setComposedSchemas(getComposedSchemas(schema));
if (ModelUtils.isArraySchema(schema)) {
CodegenProperty arrayProperty = fromProperty(name, schema, false);
m.setItems(arrayProperty.items);
m.arrayModelType = arrayProperty.complexType;
addParentContainer(m, name, schema);
} else if (ModelUtils.isIntegerSchema(schema)) { // integer type
updateModelForInteger(m, schema);
} else if (ModelUtils.isStringSchema(schema)) {
updateModelForString(m, schema);
} else if (ModelUtils.isNumberSchema(schema)) {
updateModelForNumber(m, schema);
} else if (ModelUtils.isAnyType(schema)) {
updateModelForAnyType(m, schema);
} else if (ModelUtils.isTypeObjectSchema(schema)) {
updateModelForObject(m, schema);
} else if (!ModelUtils.isNullType(schema)) {
// referenced models here, component that refs another component which is a model
// if a component references a schema which is not a generated model, the refed schema will be loaded into
// schema by unaliasSchema and one of the above code paths will be taken
}
if (schema.get$ref() != null) {
m.setRef(schema.get$ref());
}
if (ModelUtils.isComposedSchema(schema)) {
updateModelForComposedSchema(m, schema, allDefinitions);
}
// 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 (m.requiredVars != null && m.requiredVars.size() > 0) {
m.setHasRequired(true);
}
if (sortModelPropertiesByRequiredFlag) {
SortModelPropertiesByRequiredFlag(m);
}
// post process model properties
if (m.vars != null) {
for (CodegenProperty prop : m.vars) {
postProcessModelProperty(m, prop);
}
m.hasVars = !m.vars.isEmpty();
}
if (m.allVars != null) {
for (CodegenProperty prop : m.allVars) {
postProcessModelProperty(m, prop);
}
}
return m;
}
protected void SortModelPropertiesByRequiredFlag(CodegenModel model) {
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(model.vars, comparator);
Collections.sort(model.allVars, comparator);
}
protected void setAddProps(Schema schema, IJsonSchemaValidationProperties property) {
if (schema.equals(new Schema())) {
// if we are trying to set additionalProperties on an empty schema stop recursing
return;
}
// Note: This flag is set to true if additioanl properties
// is set (any type, free form object, boolean true, string, etc).
// The variable name may be renamed later to avoid confusion.
boolean additionalPropertiesIsAnyType = false;
CodegenModel m = null;
if (property instanceof CodegenModel) {
m = (CodegenModel) property;
}
CodegenProperty addPropProp = null;
boolean isAdditionalPropertiesTrue = false;
if (schema.getAdditionalProperties() == null) {
if (!disallowAdditionalPropertiesIfNotPresent) {
isAdditionalPropertiesTrue = true;
addPropProp = fromProperty(getAdditionalPropertiesName(), new Schema(), false);
additionalPropertiesIsAnyType = true;
}
} else if (schema.getAdditionalProperties() instanceof Boolean) {
if (Boolean.TRUE.equals(schema.getAdditionalProperties())) {
isAdditionalPropertiesTrue = true;
addPropProp = fromProperty(getAdditionalPropertiesName(), new Schema(), false);
additionalPropertiesIsAnyType = true;
}
} else {
// if additioanl properties is set (e.g. free form object, any type, string, etc)
addPropProp = fromProperty(getAdditionalPropertiesName(), (Schema) schema.getAdditionalProperties(), false);
additionalPropertiesIsAnyType = true;
}
if (additionalPropertiesIsAnyType) {
property.setAdditionalPropertiesIsAnyType(true);
}
if (m != null && (isAdditionalPropertiesTrue || additionalPropertiesIsAnyType)) {
m.isAdditionalPropertiesTrue = true;
}
if (ModelUtils.isComposedSchema(schema) && !supportsAdditionalPropertiesWithComposedSchema) {
return;
}
if (addPropProp != null) {
property.setAdditionalProperties(addPropProp);
}
}
/**
* 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
* @param visitedSchemas A set of visited schema names
*/
private CodegenProperty discriminatorFound(String composedSchemaName, Schema sc, String discPropName, Set visitedSchemas) {
if (visitedSchemas.contains(composedSchemaName)) { // recursive schema definition found
return null;
} else {
visitedSchemas.add(composedSchemaName);
}
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)) {
Schema composedSchema = refSchema;
if (composedSchema.getAllOf() != null) {
// If our discriminator is in one of the allOf schemas break when we find it
for (Object allOf : composedSchema.getAllOf()) {
CodegenProperty cp = discriminatorFound(composedSchemaName, (Schema) allOf, discPropName, visitedSchemas);
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 (Object oneOf : composedSchema.getOneOf()) {
String modelName = ModelUtils.getSimpleRef(((Schema) oneOf).get$ref());
CodegenProperty thisCp = discriminatorFound(composedSchemaName, (Schema) oneOf, discPropName, visitedSchemas);
if (thisCp == null) {
once(LOGGER).warn(
"'{}' defines discriminator '{}', but the referenced OneOf schema '{}' is missing {}",
composedSchemaName, discPropName, modelName, discPropName);
}
if (cp != null && cp.dataType == null) {
cp = thisCp;
continue;
}
if (cp != thisCp) {
once(LOGGER).warn(
"'{}' defines discriminator '{}', but the OneOf schema '{}' has a different {} definition than the prior OneOf schema's. Make sure the {} type and required values are the same",
composedSchemaName, discPropName, modelName, discPropName, discPropName);
}
}
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 (Object anyOf : composedSchema.getAnyOf()) {
String modelName = ModelUtils.getSimpleRef(((Schema) anyOf).get$ref());
CodegenProperty thisCp = discriminatorFound(composedSchemaName, (Schema) anyOf, discPropName, visitedSchemas);
if (thisCp == null) {
once(LOGGER).warn(
"'{}' defines discriminator '{}', but the referenced AnyOf schema '{}' is missing {}",
composedSchemaName, discPropName, modelName, discPropName);
}
if (cp != null && cp.dataType == null) {
cp = thisCp;
continue;
}
if (cp != thisCp) {
once(LOGGER).warn(
"'{}' defines discriminator '{}', but the AnyOf schema '{}' has a different {} definition than the prior AnyOf schema's. Make sure the {} type and required values are the same",
composedSchemaName, discPropName, modelName, discPropName, discPropName);
}
}
return cp;
}
}
return null;
}
/**
* Recursively look in Schema sc for the discriminator and return it
*
* @param sc The Schema that may contain the discriminator
* @param visitedSchemas An array list of visited schemas
*/
private Discriminator recursiveGetDiscriminator(Schema sc, ArrayList visitedSchemas) {
Schema refSchema = ModelUtils.getReferencedSchema(openAPI, sc);
Discriminator foundDisc = refSchema.getDiscriminator();
if (foundDisc != null) {
return foundDisc;
}
if (this.getLegacyDiscriminatorBehavior()) {
return null;
}
for (Schema s : visitedSchemas) {
if (s == refSchema) {
return null;
}
}
visitedSchemas.add(refSchema);
Discriminator disc = new Discriminator();
if (ModelUtils.isComposedSchema(refSchema)) {
Schema composedSchema = refSchema;
if (composedSchema.getAllOf() != null) {
// If our discriminator is in one of the allOf schemas break when we find it
for (Object allOf : composedSchema.getAllOf()) {
foundDisc = recursiveGetDiscriminator((Schema) allOf, visitedSchemas);
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 (Object oneOf : composedSchema.getOneOf()) {
if (ModelUtils.isNullType((Schema) oneOf)) {
// The null type does not have a discriminator. Skip.
hasNullTypeCnt++;
continue;
}
foundDisc = recursiveGetDiscriminator((Schema) oneOf, visitedSchemas);
if (foundDisc != null) {
discriminatorsPropNames.add(foundDisc.getPropertyName());
hasDiscriminatorCnt++;
}
}
if (discriminatorsPropNames.size() > 1) {
once(LOGGER).warn("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 (Object anyOf : composedSchema.getAnyOf()) {
if (ModelUtils.isNullType((Schema) anyOf)) {
// The null type does not have a discriminator. Skip.
hasNullTypeCnt++;
continue;
}
foundDisc = recursiveGetDiscriminator((Schema) anyOf, visitedSchemas);
if (foundDisc != null) {
discriminatorsPropNames.add(foundDisc.getPropertyName());
hasDiscriminatorCnt++;
}
}
if (discriminatorsPropNames.size() > 1) {
once(LOGGER).warn("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
* @return the list of oneOf and anyOf MappedModel that need to be added to the discriminator map
*/
protected List getOneOfAnyOfDescendants(String composedSchemaName, String discPropName, Schema c) {
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
once(LOGGER).warn(
"Invalid inline schema defined in oneOf/anyOf in '{}'. 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",
composedSchemaName);
}
CodegenProperty df = discriminatorFound(composedSchemaName, sc, discPropName, new TreeSet());
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";
}
}
once(LOGGER).warn("'{}' defines discriminator '{}', but the referenced schema '{}' is incorrect. {}",
composedSchemaName, discPropName, modelName, msgSuffix);
}
MappedModel mm = new MappedModel(modelName, toModelName(modelName));
descendentSchemas.add(mm);
Schema cs = ModelUtils.getSchema(openAPI, modelName);
if (cs == null) { // cannot lookup the model based on the name
once(LOGGER).error("Failed to lookup the schema '{}' when processing oneOf/anyOf. Please check to ensure it's defined properly.", modelName);
} else {
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), true);
descendentSchemas.add(mm);
}
}
}
}
return descendentSchemas;
}
protected List getAllOfDescendants(String thisSchemaName) {
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)) {
List parents = child.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);
Schema cs = schemas.get(currentSchemaName);
Map vendorExtensions = cs.getExtensions();
String mappingName =
Optional.ofNullable(vendorExtensions)
.map(ve -> ve.get("x-discriminator-value"))
.map(discriminatorValue -> (String) discriminatorValue)
.orElse(currentSchemaName);
MappedModel mm = new MappedModel(mappingName, toModelName(currentSchemaName), !mappingName.equals(currentSchemaName));
descendentSchemas.add(mm);
}
return descendentSchemas;
}
protected CodegenDiscriminator createDiscriminator(String schemaName, Schema schema) {
Discriminator sourceDiscriminator = recursiveGetDiscriminator(schema, new ArrayList());
if (sourceDiscriminator == null) {
return null;
}
CodegenDiscriminator discriminator = new CodegenDiscriminator();
String discriminatorPropertyName = sourceDiscriminator.getPropertyName();
discriminator.setPropertyName(toVarName(discriminatorPropertyName));
discriminator.setPropertyBaseName(sourceDiscriminator.getPropertyName());
discriminator.setPropertyGetter(toGetter(discriminator.getPropertyName()));
if (sourceDiscriminator.getExtensions() != null) {
discriminator.setVendorExtensions(sourceDiscriminator.getExtensions());
}
// FIXME: there are other ways to define the type of the discriminator property (inline
// for example). Handling those scenarios is too complicated for me, I'm leaving it for
// the future..
String propertyType =
Optional.ofNullable(schema.getProperties())
.map(p -> (Schema>) p.get(discriminatorPropertyName))
.map(Schema::get$ref)
.map(ModelUtils::getSimpleRef)
.map(this::toModelName)
.orElseGet(() -> typeMapping.get("string"));
discriminator.setPropertyType(propertyType);
// check to see if the discriminator property is an enum string
if (schema.getProperties() != null &&
schema.getProperties().get(discriminatorPropertyName) instanceof StringSchema) {
StringSchema s = (StringSchema) schema.getProperties().get(discriminatorPropertyName);
if (s.getEnum() != null && !s.getEnum().isEmpty()) { // it's an enum string
discriminator.setIsEnum(true);
}
}
discriminator.setMapping(sourceDiscriminator.getMapping());
List uniqueDescendants = new ArrayList<>();
if (sourceDiscriminator.getMapping() != null && !sourceDiscriminator.getMapping().isEmpty()) {
for (Entry e : sourceDiscriminator.getMapping().entrySet()) {
String name;
if (e.getValue().indexOf('/') >= 0) {
name = ModelUtils.getSimpleRef(e.getValue());
if (ModelUtils.getSchema(openAPI, name) == null) {
once(LOGGER).error("Failed to lookup the schema '{}' when processing the discriminator mapping of oneOf/anyOf. Please check to ensure it's defined properly.", name);
}
} else {
name = e.getValue();
}
uniqueDescendants.add(new MappedModel(e.getKey(), toModelName(name), true));
}
}
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);
for (MappedModel otherDescendant : otherDescendants) {
// add only if the mapping names are not the same and the model names are not the same
boolean matched = false;
for (MappedModel uniqueDescendant : uniqueDescendants) {
if (uniqueDescendant.getMappingName().equals(otherDescendant.getMappingName())
|| (uniqueDescendant.getModelName().equals(otherDescendant.getModelName()))) {
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, discriminatorPropertyName, schema);
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
* @param visitedSchemas circuit-breaker - the schemas with which the method was called before for recursive structures
*/
protected void addProperties(Map properties, List required, Schema schema, Set visitedSchemas) {
if (schema == null) {
return;
}
if (!visitedSchemas.add(schema)) {
return;
}
if (ModelUtils.isComposedSchema(schema)) {
// fix issue #16797 and #15796, constructor fail by missing parent required params
if (schema.getProperties() != null && !schema.getProperties().isEmpty()) {
properties.putAll(schema.getProperties());
}
if (schema.getAllOf() != null) {
for (Object component : schema.getAllOf()) {
addProperties(properties, required, (Schema) component, visitedSchemas);
}
}
if (schema.getRequired() != null) {
required.addAll(schema.getRequired());
}
if (schema.getOneOf() != null) {
for (Object component : schema.getOneOf()) {
addProperties(properties, required, (Schema) component, visitedSchemas);
}
}
if (schema.getAnyOf() != null) {
for (Object component : schema.getAnyOf()) {
addProperties(properties, required, (Schema) component, visitedSchemas);
}
}
for (String r : required) {
if (!properties.containsKey(r)) {
LOGGER.error("Required var {} not in properties", r);
}
}
return;
}
if (StringUtils.isNotBlank(schema.get$ref())) {
Schema interfaceSchema = ModelUtils.getReferencedSchema(this.openAPI, schema);
addProperties(properties, required, interfaceSchema, visitedSchemas);
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));
}
protected void updatePropertyForMap(CodegenProperty property, Schema p) {
// throw exception if additionalProperties is false
if (p.getAdditionalProperties() instanceof Boolean && Boolean.FALSE.equals(p.getAdditionalProperties())) {
throw new RuntimeException("additionalProperties cannot be false in updatePropertyForMap.");
}
property.isContainer = true;
property.containerType = "map";
property.containerTypeMapped = typeMapping.get(property.containerType);
// 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(ModelUtils.getAdditionalProperties(p));
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, false);
updatePropertyForMap(property, cp);
}
protected void updatePropertyForObject(CodegenProperty property, Schema p) {
if (ModelUtils.isFreeFormObject(p, openAPI)) {
// non-composed object type with no properties + additionalProperties
// additionalProperties must be null, ObjectSchema, or empty Schema
property.isFreeFormObject = true;
if (languageSpecificPrimitives.contains(property.dataType)) {
property.isPrimitiveType = true;
}
if (ModelUtils.isMapSchema(p)) {
// an object or anyType composed schema that has additionalProperties set
updatePropertyForMap(property, p);
} else {
// ObjectSchema with additionalProperties = null, can be nullable
property.setIsMap(false);
}
} else if (ModelUtils.isMapSchema(p)) {
// an object or anyType composed schema that has additionalProperties set
updatePropertyForMap(property, p);
}
addVarsRequiredVarsAdditionalProps(p, property);
}
protected void updatePropertyForAnyType(CodegenProperty property, Schema 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 = property.isNullable ||
!(ModelUtils.isComposedSchema(p)) ||
p.getAllOf() == null ||
p.getAllOf().isEmpty();
if (languageSpecificPrimitives.contains(property.dataType)) {
property.isPrimitiveType = true;
}
if (ModelUtils.isMapSchema(p)) {
// an object or anyType composed schema that has additionalProperties set
// some of our code assumes that any type schema with properties defined will be a map
// even though it should allow in any type and have map constraints for properties
updatePropertyForMap(property, p);
}
addVarsRequiredVarsAdditionalProps(p, property);
}
protected void updatePropertyForString(CodegenProperty property, Schema p) {
if (ModelUtils.isByteArraySchema(p)) {
property.setIsString(false);
property.isByteArray = true;
} else if (ModelUtils.isBinarySchema(p)) {
property.isBinary = true;
property.isFile = true; // file = binary in OAS3
} else if (ModelUtils.isUUIDSchema(p)) {
property.isUuid = true;
} else if (ModelUtils.isURISchema(p)) {
property.isUri = true;
} else if (ModelUtils.isEmailSchema(p)) {
property.isEmail = true;
} else if (ModelUtils.isPasswordSchema(p)) {
property.isPassword = true;
} else if (ModelUtils.isDateSchema(p)) { // date format
property.setIsString(false); // for backward compatibility with 2.x
property.isDate = true;
} else if (ModelUtils.isDateTimeSchema(p)) { // date-time format
property.setIsString(false); // for backward compatibility with 2.x
property.isDateTime = true;
} else if (ModelUtils.isDecimalSchema(p)) { // type: string, format: number
property.isDecimal = true;
property.setIsString(false);
}
property.pattern = toRegularExpression(p.getPattern());
}
protected void updatePropertyForNumber(CodegenProperty property, Schema p) {
property.isNumeric = Boolean.TRUE;
if (ModelUtils.isFloatSchema(p)) { // float
property.isFloat = Boolean.TRUE;
} else if (ModelUtils.isDoubleSchema(p)) { // double
property.isDouble = Boolean.TRUE;
}
}
protected void updatePropertyForInteger(CodegenProperty property, Schema p) {
property.isNumeric = Boolean.TRUE;
if (ModelUtils.isLongSchema(p)) { // int64/long format
property.isLong = Boolean.TRUE;
} else {
property.isInteger = Boolean.TRUE; // older use case, int32 and unbounded int
if (ModelUtils.isShortSchema(p)) { // int32
property.setIsShort(Boolean.TRUE);
}
}
}
/**
* TODO remove this in 7.0.0 as a breaking change
* This method was kept when required was added to the fromProperty signature
* to ensure that the change was non-breaking
*
* @param name name of the property
* @param p OAS property schema
* @param required true if the property is required in the next higher object schema, false otherwise
* @return Codegen Property object
*/
public CodegenProperty fromProperty(String name, Schema p, boolean required) {
return fromProperty(name, p, required, false);
}
/**
* TODO remove this in 7.0.0 as a breaking change
* This method was kept when required was added to the fromProperty signature
* to ensure that the change was non-breaking
*
* @param name name of the property
* @param p OAS property schema
* @return Codegen Property object
*/
public CodegenProperty fromProperty(String name, Schema p) {
return fromProperty(name, p, false, false);
}
/**
* 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
* @param required true if the property is required in the next higher object schema, false otherwise
* @param schemaIsFromAdditionalProperties true if the property is a required property defined by additional properties schema
* If this is the actual additionalProperties schema not defining a required property, then
* the value should be false
* @return Codegen Property object
*/
public CodegenProperty fromProperty(String name, Schema p, boolean required, boolean schemaIsFromAdditionalProperties) {
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, required, schemaIsFromAdditionalProperties);
CodegenProperty cpc = schemaCodegenPropertyCache.get(ns);
if (cpc != null) {
LOGGER.debug("Cached fromProperty for {} : {} required={}", name, p.getName(), required);
return cpc;
}
// if it's ref to schema's properties, get the actual schema defined in the properties
Schema refToPropertiesSchema = ModelUtils.getSchemaFromRefToSchemaWithProperties(openAPI, p.get$ref());
if (refToPropertiesSchema != null) {
p = refToPropertiesSchema;
return fromProperty(name, refToPropertiesSchema, required, schemaIsFromAdditionalProperties);
}
Schema original = null;
// check if it's allOf (only 1 sub schema) with or without default/nullable/etc set in the top level
if (ModelUtils.isAllOf(p) && p.getAllOf().size() == 1) {
if (p.getAllOf().get(0) instanceof Schema) {
original = p;
p = (Schema) p.getAllOf().get(0);
} else {
LOGGER.error("Unknown type in allOf schema. Please report the issue via openapi-generator's Github issue tracker.");
}
} else if (p.get$ref() != null) { // it's a ref
original = p;
}
CodegenProperty property = CodegenModelFactory.newInstance(CodegenModelType.PROPERTY);
if (p.equals(trueSchema)) {
property.setIsBooleanSchemaTrue(true);
} else if (p.equals(falseSchema)) {
property.setIsBooleanSchemaFalse(true);
}
// unalias schema
p = unaliasSchema(p);
property.setSchemaIsFromAdditionalProperties(schemaIsFromAdditionalProperties);
property.required = required;
ModelUtils.syncValidationProperties(p, property);
property.setFormat(p.getFormat());
property.name = toVarName(name);
property.baseName = name;
property.setHasSanitizedName(!property.baseName.equals(property.name));
if (ModelUtils.getType(p) == null) {
property.openApiType = getSchemaType(p);
} else {
property.openApiType = ModelUtils.getType(p);
}
property.nameInPascalCase = camelize(property.name);
property.nameInCamelCase = camelize(property.name, LOWERCASE_FIRST_LETTER);
property.nameInSnakeCase = CaseFormat.UPPER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, property.nameInPascalCase);
property.description = escapeText(p.getDescription());
property.unescapedDescription = p.getDescription();
property.title = p.getTitle();
property.getter = toGetter(name);
property.setter = toSetter(name);
// put toExampleValue in a try-catch block to log the error as example values are not critical
try {
property.example = toExampleValue(p);
} catch (Exception e) {
LOGGER.error("Error in generating `example` for the property {}. Default to ERROR_TO_EXAMPLE_VALUE. Enable debugging for more info.", property.baseName);
LOGGER.debug("Exception from toExampleValue: {}", e.getMessage());
property.example = "ERROR_TO_EXAMPLE_VALUE";
}
property.jsonSchema = Json.pretty(Json.mapper().convertValue(p, TreeMap.class));
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.getExtensions() != null && !p.getExtensions().isEmpty()) {
property.getVendorExtensions().putAll(p.getExtensions());
} else if (p.get$ref() != null) {
Schema referencedSchema = ModelUtils.getReferencedSchema(this.openAPI, p);
if (referencedSchema.getExtensions() != null && !referencedSchema.getExtensions().isEmpty()) {
property.getVendorExtensions().putAll(referencedSchema.getExtensions());
}
}
//Inline enum case:
if (p.getEnum() != null && !p.getEnum().isEmpty()) {
List _enum = p.getEnum();
property._enum = new ArrayList<>();
for (Object i : _enum) {
// raw null values in enums are unions for nullable
// attributes, not actual enum values, so we remove them here
if (i == null) {
property.isNullable = true;
continue;
}
property._enum.add(String.valueOf(i));
}
property.isEnum = true;
property.isInnerEnum = 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 != p && referencedSchema.getEnum() != null && !referencedSchema.getEnum().isEmpty()) {
List _enum = referencedSchema.getEnum();
property.isEnumRef = true;
Map allowableValues = new HashMap<>();
allowableValues.put("values", _enum);
if (allowableValues.size() > 0) {
property.allowableValues = allowableValues;
}
}
// set isNullable using nullable or x-nullable in the schema
if (referencedSchema.getNullable() != null) {
property.isNullable = referencedSchema.getNullable();
} else if (referencedSchema.getExtensions() != null &&
referencedSchema.getExtensions().containsKey("x-nullable")) {
property.isNullable = (Boolean) referencedSchema.getExtensions().get("x-nullable");
}
final XML referencedSchemaXml = referencedSchema.getXml();
if (referencedSchemaXml != null) {
property.xmlName = referencedSchemaXml.getName();
property.xmlNamespace = referencedSchemaXml.getNamespace();
property.xmlPrefix = referencedSchemaXml.getPrefix();
if (referencedSchemaXml.getAttribute() != null) {
property.isXmlAttribute = referencedSchemaXml.getAttribute();
}
if (referencedSchemaXml.getWrapped() != null) {
property.isXmlWrapped = referencedSchemaXml.getWrapped();
}
}
if (p.getXml() != null) {
if (p.getXml().getAttribute() != null) {
property.isXmlAttribute = p.getXml().getAttribute();
}
if (p.getXml().getWrapped() != null) {
property.isXmlWrapped = p.getXml().getWrapped();
}
property.xmlPrefix = p.getXml().getPrefix();
property.xmlName = p.getXml().getName();
property.xmlNamespace = p.getXml().getNamespace();
}
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;
}
property.setTypeProperties(p, openAPI);
property.setComposedSchemas(getComposedSchemas(p));
if (ModelUtils.isIntegerSchema(p)) { // integer type
updatePropertyForInteger(property, p);
} else if (ModelUtils.isBooleanSchema(p)) { // boolean type
property.getter = toBooleanGetter(name);
} else if (ModelUtils.isFileSchema(p) && !ModelUtils.isStringSchema(p)) {
// swagger v2 only, type file
property.isFile = true;
} else if (ModelUtils.isStringSchema(p)) {
updatePropertyForString(property, p);
} else if (ModelUtils.isNumberSchema(p)) {
updatePropertyForNumber(property, p);
} else if (ModelUtils.isArraySchema(p)) {
// default to string if inner item is undefined
property.isContainer = true;
if (ModelUtils.isSet(p)) {
property.containerType = "set";
property.containerTypeMapped = typeMapping.get(property.containerType);
} else {
property.containerType = "array";
property.containerTypeMapped = typeMapping.get(property.containerType);
}
property.baseType = getSchemaType(p);
// handle inner property
String itemName = getItemsName(p, name);
Schema innerSchema = unaliasSchema(ModelUtils.getSchemaItems(p));
CodegenProperty cp = fromProperty(itemName, innerSchema, false);
updatePropertyForArray(property, cp);
} else if (ModelUtils.isTypeObjectSchema(p)) {
updatePropertyForObject(property, p);
} else if (ModelUtils.isAnyType(p)) {
updatePropertyForAnyType(property, p);
} else if (!ModelUtils.isNullType(p)) {
// referenced model
}
if (p.get$ref() != null) {
property.setRef(p.get$ref());
}
boolean isAnyTypeWithNothingElseSet = (ModelUtils.isAnyType(p) &&
(p.getProperties() == null || p.getProperties().isEmpty()) &&
!ModelUtils.isComposedSchema(p) &&
p.getAdditionalProperties() == null && p.getNot() == null && p.getEnum() == null);
if (!ModelUtils.isArraySchema(p) && !ModelUtils.isMapSchema(p) && !ModelUtils.isFreeFormObject(p, openAPI) && !isAnyTypeWithNothingElseSet) {
/* schemas that are not Array, not ModelUtils.isMapSchema, not isFreeFormObject, not AnyType with nothing else set
* so primitive schemas int, str, number, referenced schemas, AnyType schemas with properties, enums, or composition
*/
String type = getSchemaType(p);
setNonArrayMapProperty(property, type);
property.isModel = (ModelUtils.isComposedSchema(referencedSchema) || ModelUtils.isObjectSchema(referencedSchema)) && ModelUtils.isModel(referencedSchema);
}
// restore original schema with default value, nullable, readonly etc
if (original != null) {
p = original;
// evaluate common attributes if defined in the top level
if (p.getNullable() != null) {
property.isNullable = p.getNullable();
} else if (p.getExtensions() != null && p.getExtensions().containsKey("x-nullable")) {
property.isNullable = (Boolean) p.getExtensions().get("x-nullable");
}
if (p.getReadOnly() != null) {
property.isReadOnly = p.getReadOnly();
}
if (p.getWriteOnly() != null) {
property.isWriteOnly = p.getWriteOnly();
}
if (original.getExtensions() != null) {
property.getVendorExtensions().putAll(original.getExtensions());
}
if (original.getDeprecated() != null) {
property.deprecated = p.getDeprecated();
}
if (original.getDescription() != null) {
property.description = escapeText(p.getDescription());
property.unescapedDescription = p.getDescription();
}
if (original.getMaxLength() != null) {
property.setMaxLength(original.getMaxLength());
}
if (original.getMinLength() != null) {
property.setMinLength(original.getMinLength());
}
if (original.getMaxItems() != null) {
property.setMaxItems(original.getMaxItems());
}
if (original.getMinItems() != null) {
property.setMinItems(original.getMinItems());
}
if (original.getMaximum() != null) {
property.setMaximum(String.valueOf(original.getMaximum().doubleValue()));
}
if (original.getMinimum() != null) {
property.setMinimum(String.valueOf(original.getMinimum().doubleValue()));
}
if (original.getTitle() != null) {
property.setTitle(original.getTitle());
}
}
// set the default value
property.defaultValue = toDefaultValue(property, p);
property.defaultValueWithParam = toDefaultValueWithParam(name, p);
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) {
if (LOGGER.isWarnEnabled()) {
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;
property.isInnerEnum = 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) {
if (LOGGER.isWarnEnabled()) {
LOGGER.warn("skipping invalid map property {}", Json.pretty(property));
}
return;
}
if (!languageSpecificPrimitives.contains(innerProperty.baseType)) {
property.complexType = innerProperty.baseType;
} else {
property.isPrimitiveType = true;
}
// TODO fix this, map should not be assigning properties to items
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;
property.isInnerEnum = 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 && currentProperty.isEnum;
}
protected CodegenProperty getMostInnerItems(CodegenProperty property) {
CodegenProperty currentProperty = property;
while (currentProperty != null && (Boolean.TRUE.equals(currentProperty.isMap)
|| Boolean.TRUE.equals(currentProperty.isArray)) && currentProperty.items != null) {
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 schemaMappings mappings of external types to be omitted by unaliasing
*/
protected void handleMethodResponse(Operation operation,
Map schemas,
CodegenOperation op,
ApiResponse methodResponse,
Map schemaMappings) {
Schema responseSchema = unaliasSchema(ModelUtils.getSchemaFromResponse(openAPI, methodResponse));
if (responseSchema != null) {
CodegenProperty cm = fromProperty("response", responseSchema, false);
if (ModelUtils.isArraySchema(responseSchema)) {
CodegenProperty innerProperty = fromProperty("response", ModelUtils.getSchemaItems(responseSchema), false);
op.returnBaseType = innerProperty.baseType;
} else if (ModelUtils.isMapSchema(responseSchema)) {
CodegenProperty innerProperty = fromProperty("response", ModelUtils.getAdditionalProperties(responseSchema), false);
op.returnBaseType = innerProperty.baseType;
} else {
if (cm.complexType != null) {
op.returnBaseType = cm.complexType;
} else {
op.returnBaseType = cm.baseType;
}
}
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;
}
op.returnProperty = cm;
}
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
*/
@Override
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();
op.operationId = getOrGenerateOperationId(operation, path, httpMethod);
if (isStrictSpecBehavior() && !path.startsWith("/")) {
// modifies an operation.path to strictly conform to OpenAPI Spec
op.path = "/" + path;
} else {
op.path = path;
}
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 (Entry operationGetResponsesEntry : operation.getResponses().entrySet()) {
String key = operationGetResponsesEntry.getKey();
ApiResponse response = operationGetResponsesEntry.getValue();
addProducesInfo(response, op);
CodegenResponse r = fromResponse(key, response);
Map headers = response.getHeaders();
if (headers != null) {
List responseHeaders = new ArrayList<>();
for (Entry entry : headers.entrySet()) {
String headerName = entry.getKey();
Header header = ModelUtils.getReferencedHeader(this.openAPI, entry.getValue());
CodegenParameter responseHeader = headerToCodegenParameter(header, headerName, imports, String.format(Locale.ROOT, "%sResponseParameter", r.code));
responseHeaders.add(responseHeader);
}
r.setResponseHeaders(responseHeaders);
}
String mediaTypeSchemaSuffix = String.format(Locale.ROOT, "%sResponseBody", r.code);
r.setContent(getContent(response.getContent(), imports, mediaTypeSchemaSuffix));
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;
}
if (Boolean.TRUE.equals(r.isDefault)) {
op.defaultReturnType = Boolean.TRUE;
}
// check if any 4xx or 5xx response has an error response object defined
if ((Boolean.TRUE.equals(r.is4xx) || Boolean.TRUE.equals(r.is5xx)) &&
Boolean.FALSE.equals(r.primitiveType) && Boolean.FALSE.equals(r.simpleType)) {
op.hasErrorResponseObject = Boolean.TRUE;
}
}
// check if the operation can both return a 2xx response with a body and without
if (op.responses.stream().anyMatch(response -> response.is2xx && response.dataType != null) &&
op.responses.stream().anyMatch(response -> response.is2xx && response.dataType == null)) {
op.isResponseOptional = Boolean.TRUE;
}
op.responses.sort((a, b) -> {
int aScore = a.isWildcard() ? 2 : a.isRange() ? 1 : 0;
int bScore = b.isWildcard() ? 2 : b.isRange() ? 1 : 0;
return Integer.compare(aScore, bScore);
});
if (methodResponse != null) {
handleMethodResponse(operation, schemas, op, methodResponse, importMapping);
}
}
// check skipOperationExample, which can be set to true to avoid out of memory errors for large spec
if (!isSkipOperationExample() && operation.getResponses() != null) {
// generate examples
ExampleGenerator generator = new ExampleGenerator(schemas, this.openAPI);
List> examples = new ArrayList<>();
for (String statusCode : operation.getResponses().keySet()) {
ApiResponse apiResponse = operation.getResponses().get(statusCode);
Schema schema = unaliasSchema(ModelUtils.getSchemaFromResponse(openAPI, apiResponse));
if (schema == null) {
continue;
}
if (apiResponse.getContent() != null) {
Set producesInfo = new ConcurrentSkipListSet<>(apiResponse.getContent().keySet());
String exampleStatusCode = statusCode;
if (exampleStatusCode.equals("default")) {
exampleStatusCode = "200";
}
List> examplesForResponse = generator.generateFromResponseSchema(exampleStatusCode, schema, producesInfo);
if (examplesForResponse != null) {
examples.addAll(examplesForResponse);
}
}
}
op.examples = examples;
}
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 cookieParams = new ArrayList<>();
List formParams = new ArrayList<>();
List requiredParams = new ArrayList<>();
List optionalParams = new ArrayList<>();
List requiredAndNotNullableParams = new ArrayList<>();
List notNullableParams = new ArrayList<>();
CodegenParameter bodyParam = null;
RequestBody requestBody = ModelUtils.getReferencedRequestBody(this.openAPI, operation.getRequestBody());
if (requestBody != null) {
String contentType = getContentType(requestBody);
if (contentType != null) {
contentType = contentType.toLowerCase(Locale.ROOT);
}
if (contentType != null &&
((!(this instanceof RustAxumServerCodegen) && contentType.startsWith("application/x-www-form-urlencoded")) ||
contentType.startsWith("multipart"))) {
// process form parameters
formParams = fromRequestBodyToFormParameters(requestBody, imports);
op.isMultipart = contentType.startsWith("multipart");
for (CodegenParameter cp : formParams) {
setParameterEncodingValues(cp, requestBody.getContent().get(contentType));
postProcessParameter(cp);
}
// add form parameters to the beginning of all parameter list
if (prependFormOrBodyParameters) {
for (CodegenParameter cp : formParams) {
allParams.add(cp.copy());
}
}
} else {
// process body parameter
String bodyParameterName = "";
if (op.vendorExtensions != null && op.vendorExtensions.containsKey("x-codegen-request-body-name")) {
bodyParameterName = (String) op.vendorExtensions.get("x-codegen-request-body-name");
}
if (requestBody.getExtensions() != null && requestBody.getExtensions().containsKey("x-codegen-request-body-name")) {
bodyParameterName = (String) requestBody.getExtensions().get("x-codegen-request-body-name");
}
bodyParam = fromRequestBody(requestBody, imports, bodyParameterName);
if (bodyParam != null) {
bodyParam.description = escapeText(requestBody.getDescription());
postProcessParameter(bodyParam);
bodyParams.add(bodyParam);
if (prependFormOrBodyParameters) {
allParams.add(bodyParam);
}
// add example
if (schemas != null && !isSkipOperationExample()) {
op.requestBodyExamples = new ExampleGenerator(schemas, this.openAPI).generate(null, new ArrayList<>(getConsumesInfo(this.openAPI, operation)), bodyParam.baseType);
}
}
}
}
if (parameters != null) {
for (Parameter param : parameters) {
param = ModelUtils.getReferencedParameter(this.openAPI, param);
CodegenParameter p = fromParameter(param, imports);
p.setContent(getContent(param.getContent(), imports, "RequestParameter" + toModelName(param.getName())));
// ensure unique params
if (ensureUniqueParams) {
while (!isParameterNameUnique(p, allParams)) {
p.paramName = generateNextName(p.paramName);
}
}
allParams.add(p);
if (param instanceof QueryParameter || "query".equalsIgnoreCase(param.getIn())) {
queryParams.add(p.copy());
} else if (param instanceof PathParameter || "path".equalsIgnoreCase(param.getIn())) {
pathParams.add(p.copy());
} else if (param instanceof HeaderParameter || "header".equalsIgnoreCase(param.getIn())) {
headerParams.add(p.copy());
} else if (param instanceof CookieParameter || "cookie".equalsIgnoreCase(param.getIn())) {
cookieParams.add(p.copy());
} else {
LOGGER.warn("Unknown parameter type {} for {}", p.baseType, p.baseName);
}
}
}
// add form/body parameter (if any) to the end of all parameter list
if (!prependFormOrBodyParameters) {
for (CodegenParameter cp : formParams) {
if (ensureUniqueParams) {
while (!isParameterNameUnique(cp, allParams)) {
cp.paramName = generateNextName(cp.paramName);
}
}
allParams.add(cp.copy());
}
for (CodegenParameter cp : bodyParams) {
if (ensureUniqueParams) {
while (!isParameterNameUnique(cp, allParams)) {
cp.paramName = generateNextName(cp.paramName);
}
}
allParams.add(cp.copy());
}
}
// create optional, required parameters
for (CodegenParameter cp : allParams) {
if (cp.required) { //required parameters
requiredParams.add(cp.copy());
} else { // optional parameters
optionalParams.add(cp.copy());
op.hasOptionalParams = true;
}
if (cp.requiredAndNotNullable()) {
requiredAndNotNullableParams.add(cp.copy());
}
if (!cp.isNullable) {
notNullableParams.add(cp.copy());
}
}
// add imports to operation import tag
for (String i : imports) {
if (needToImport(i)) {
op.imports.add(i);
}
}
op.bodyParam = bodyParam;
op.httpMethod = httpMethod.toUpperCase(Locale.ROOT);
// move "required" parameters in front of "optional" parameters
if (sortParamsByRequiredFlag) {
SortParametersByRequiredFlag(allParams);
}
op.allParams = allParams;
op.bodyParams = bodyParams;
op.pathParams = pathParams;
op.queryParams = queryParams;
op.headerParams = headerParams;
op.cookieParams = cookieParams;
op.formParams = formParams;
op.requiredParams = requiredParams;
op.optionalParams = optionalParams;
op.requiredAndNotNullableParams = requiredAndNotNullableParams;
op.notNullableParams = notNullableParams;
op.externalDocs = operation.getExternalDocs();
// legacy support
op.nickname = op.operationId;
if (op.allParams.size() > 0) {
op.hasParams = true;
}
op.hasRequiredParams = op.requiredParams.size() > 0;
// check if the operation has only a single parameter
op.hasSingleParam = op.allParams.size() == 1;
// set Restful Flag
op.isRestfulShow = op.isRestfulShow();
op.isRestfulIndex = op.isRestfulIndex();
op.isRestfulCreate = op.isRestfulCreate();
op.isRestfulUpdate = op.isRestfulUpdate();
op.isRestfulDestroy = op.isRestfulDestroy();
op.isRestful = op.isRestful();
return op;
}
public void SortParametersByRequiredFlag(List parameters) {
parameters.sort((one, another) -> {
if (one.required == another.required)
return 0;
else if (one.required)
return -1;
else
return 1;
});
}
public boolean isParameterNameUnique(CodegenParameter p, List parameters) {
for (CodegenParameter parameter : parameters) {
if (System.identityHashCode(p) == System.identityHashCode(parameter)) {
continue; // skip itself
}
if (p.paramName.equals(parameter.paramName)) {
return false;
}
}
return true;
}
/**
* Convert OAS Response object to Codegen Response object
*
* @param responseCode HTTP response code
* @param response OAS Response object
* @return Codegen Response object
*/
public CodegenResponse fromResponse(String responseCode, ApiResponse response) {
CodegenResponse r = CodegenModelFactory.newInstance(CodegenModelType.RESPONSE);
if ("default".equals(responseCode) || "defaultResponse".equals(responseCode)) {
r.code = "0";
r.isDefault = true;
} else {
r.code = responseCode;
switch (r.code.charAt(0)) {
case '1':
r.is1xx = true;
break;
case '2':
r.is2xx = true;
break;
case '3':
r.is3xx = true;
break;
case '4':
r.is4xx = true;
break;
case '5':
r.is5xx = true;
break;
default:
throw new RuntimeException("Invalid response code " + responseCode);
}
}
Schema responseSchema;
if (this.openAPI != null && this.openAPI.getComponents() != null) {
responseSchema = unaliasSchema(ModelUtils.getSchemaFromResponse(openAPI, response));
} else { // no model/alias defined
responseSchema = ModelUtils.getSchemaFromResponse(openAPI, response);
}
r.schema = responseSchema;
r.message = escapeText(response.getDescription());
// TODO need to revise and test examples in responses
// ApiResponse does not support examples at the moment
//r.examples = toExamples(response.getExamples());
r.jsonSchema = Json.pretty(response);
if (response.getExtensions() != null && !response.getExtensions().isEmpty()) {
r.vendorExtensions.putAll(response.getExtensions());
}
addHeaders(response, r.headers);
r.hasHeaders = !r.headers.isEmpty();
if (r.schema == null) {
r.primitiveType = true;
r.simpleType = true;
return r;
}
ModelUtils.syncValidationProperties(responseSchema, r);
if (responseSchema.getPattern() != null) {
r.setPattern(toRegularExpression(responseSchema.getPattern()));
}
CodegenProperty cp = fromProperty("response", responseSchema, false);
r.dataType = getTypeDeclaration(responseSchema);
r.returnProperty = cp;
if (!ModelUtils.isArraySchema(responseSchema)) {
if (cp.complexType != null) {
if (cp.items != null) {
r.baseType = cp.items.complexType;
} else {
r.baseType = cp.complexType;
}
r.isModel = true;
} else {
r.baseType = cp.baseType;
}
}
r.setTypeProperties(responseSchema, openAPI);
r.setComposedSchemas(getComposedSchemas(responseSchema));
if (ModelUtils.isArraySchema(responseSchema)) {
r.simpleType = false;
r.isArray = true;
r.containerType = cp.containerType;
r.containerTypeMapped = typeMapping.get(cp.containerType);
CodegenProperty items = fromProperty("response", ModelUtils.getSchemaItems(responseSchema), false);
r.setItems(items);
CodegenProperty innerCp = items;
while (innerCp != null) {
r.baseType = innerCp.baseType;
innerCp = innerCp.items;
}
} else if (ModelUtils.isFileSchema(responseSchema) && !ModelUtils.isStringSchema(responseSchema)) {
// swagger v2 only, type file
r.isFile = true;
} else if (ModelUtils.isStringSchema(responseSchema)) {
if (ModelUtils.isEmailSchema(responseSchema)) {
r.isEmail = true;
} else if (ModelUtils.isPasswordSchema(responseSchema)) {
r.isPassword = true;
} else if (ModelUtils.isUUIDSchema(responseSchema)) {
r.isUuid = true;
} else if (ModelUtils.isByteArraySchema(responseSchema)) {
r.setIsString(false);
r.isByteArray = true;
} else if (ModelUtils.isBinarySchema(responseSchema)) {
r.isFile = true; // file = binary in OAS3
r.isBinary = true;
} else if (ModelUtils.isDateSchema(responseSchema)) {
r.setIsString(false); // for backward compatibility with 2.x
r.isDate = true;
} else if (ModelUtils.isDateTimeSchema(responseSchema)) {
r.setIsString(false); // for backward compatibility with 2.x
r.isDateTime = true;
} else if (ModelUtils.isDecimalSchema(responseSchema)) { // type: string, format: number
r.isDecimal = true;
r.setIsString(false);
r.isNumeric = true;
}
} else if (ModelUtils.isIntegerSchema(responseSchema)) { // integer type
r.isNumeric = Boolean.TRUE;
if (ModelUtils.isLongSchema(responseSchema)) { // int64/long format
r.isLong = Boolean.TRUE;
} else {
r.isInteger = Boolean.TRUE; // older use case, int32 and unbounded int
if (ModelUtils.isShortSchema(responseSchema)) { // int32
r.setIsShort(Boolean.TRUE);
}
}
} else if (ModelUtils.isNumberSchema(responseSchema)) {
r.isNumeric = Boolean.TRUE;
if (ModelUtils.isFloatSchema(responseSchema)) { // float
r.isFloat = Boolean.TRUE;
} else if (ModelUtils.isDoubleSchema(responseSchema)) { // double
r.isDouble = Boolean.TRUE;
}
} else if (ModelUtils.isTypeObjectSchema(responseSchema)) {
if (ModelUtils.isFreeFormObject(responseSchema, openAPI)) {
r.isFreeFormObject = true;
} else {
r.isModel = true;
}
r.simpleType = false;
r.containerType = cp.containerType;
r.containerTypeMapped = cp.containerTypeMapped;
addVarsRequiredVarsAdditionalProps(responseSchema, r);
} else if (ModelUtils.isAnyType(responseSchema)) {
addVarsRequiredVarsAdditionalProps(responseSchema, r);
} else if (!ModelUtils.isBooleanSchema(responseSchema)) {
// referenced schemas
LOGGER.debug("Property type is not primitive: {}", cp.dataType);
}
r.primitiveType = (r.baseType == null || languageSpecificPrimitives().contains(r.baseType));
if (r.baseType == null) {
r.isMap = false;
r.isArray = false;
r.primitiveType = true;
r.simpleType = true;
}
postProcessResponseWithProperty(r, cp);
return r;
}
/**
* Convert OAS Callback object to Codegen Callback object
*
* @param name callback name
* @param callback OAS Callback object
* @param servers list of servers
* @return Codegen Response object
*/
public CodegenCallback fromCallback(String name, Callback callback, List servers) {
CodegenCallback c = new CodegenCallback();
c.name = name;
if (callback.getExtensions() != null && !callback.getExtensions().isEmpty()) {
c.vendorExtensions.putAll(callback.getExtensions());
}
callback.forEach((expression, pi) -> {
CodegenCallback.Url u = new CodegenCallback.Url();
u.expression = expression;
if (pi.getExtensions() != null && !pi.getExtensions().isEmpty()) {
u.vendorExtensions.putAll(pi.getExtensions());
}
Stream.of(
Pair.of("get", pi.getGet()),
Pair.of("head", pi.getHead()),
Pair.of("put", pi.getPut()),
Pair.of("post", pi.getPost()),
Pair.of("delete", pi.getDelete()),
Pair.of("patch", pi.getPatch()),
Pair.of("options", pi.getOptions()))
.filter(p -> p.getValue() != null)
.forEach(p -> {
String method = p.getKey();
Operation op = p.getValue();
if (op.getExtensions() != null && Boolean.TRUE.equals(op.getExtensions().get("x-internal"))) {
// skip operation if x-internal sets to true
LOGGER.info("Operation ({} {} - {}) not generated since x-internal is set to true",
method, expression, op.getOperationId());
} else {
boolean genId = op.getOperationId() == null;
if (genId) {
op.setOperationId(getOrGenerateOperationId(op, c.name + "_" + expression.replaceAll("\\{\\$.*}", ""), method));
}
if (op.getExtensions() == null) {
op.setExtensions(new HashMap<>());
}
// This extension will be removed later by `fromOperation()` as it is only needed here to
// distinguish between normal operations and callback requests
op.getExtensions().put("x-callback-request", true);
CodegenOperation co = fromOperation(expression, method, op, servers);
if (genId) {
co.operationIdOriginal = null;
// legacy (see `fromOperation()`)
co.nickname = co.operationId;
}
u.requests.add(co);
}
});
c.urls.add(u);
});
return c;
}
private void finishUpdatingParameter(CodegenParameter codegenParameter, Parameter parameter) {
// default to UNKNOWN_PARAMETER_NAME if paramName is null
if (codegenParameter.paramName == null) {
LOGGER.warn("Parameter name not defined properly. Default to UNKNOWN_PARAMETER_NAME");
codegenParameter.paramName = "UNKNOWN_PARAMETER_NAME";
}
// set the parameter example value
// should be overridden by lang codegen
setParameterExampleValue(codegenParameter, parameter);
// set the parameter examples (if available)
setParameterExamples(codegenParameter, parameter);
postProcessParameter(codegenParameter);
LOGGER.debug("debugging codegenParameter return: {}", codegenParameter);
}
private void updateParameterForMap(CodegenParameter codegenParameter, Schema parameterSchema, Set imports) {
CodegenProperty codegenProperty = fromProperty("inner", ModelUtils.getAdditionalProperties(parameterSchema), false);
codegenParameter.items = codegenProperty;
codegenParameter.mostInnerItems = codegenProperty.mostInnerItems;
codegenParameter.baseType = codegenProperty.dataType;
codegenParameter.isContainer = true;
codegenParameter.isMap = true;
// recursively add import
while (codegenProperty != null) {
imports.add(codegenProperty.baseType);
codegenProperty = codegenProperty.items;
}
}
protected void updateParameterForString(CodegenParameter codegenParameter, Schema parameterSchema) {
if (ModelUtils.isEmailSchema(parameterSchema)) {
codegenParameter.isEmail = true;
} else if (ModelUtils.isUUIDSchema(parameterSchema)) {
codegenParameter.isUuid = true;
} else if (ModelUtils.isByteArraySchema(parameterSchema)) {
codegenParameter.setIsString(false);
codegenParameter.isByteArray = true;
codegenParameter.isPrimitiveType = true;
} else if (ModelUtils.isBinarySchema(parameterSchema)) {
codegenParameter.isBinary = true;
codegenParameter.isFile = true; // file = binary in OAS3
codegenParameter.isPrimitiveType = true;
} else if (ModelUtils.isDateSchema(parameterSchema)) {
codegenParameter.setIsString(false); // for backward compatibility with 2.x
codegenParameter.isDate = true;
codegenParameter.isPrimitiveType = true;
} else if (ModelUtils.isDateTimeSchema(parameterSchema)) {
codegenParameter.setIsString(false); // for backward compatibility with 2.x
codegenParameter.isDateTime = true;
codegenParameter.isPrimitiveType = true;
} else if (ModelUtils.isDecimalSchema(parameterSchema)) { // type: string, format: number
codegenParameter.setIsString(false);
codegenParameter.isDecimal = true;
codegenParameter.isPrimitiveType = true;
}
if (Boolean.TRUE.equals(codegenParameter.isString)) {
codegenParameter.isPrimitiveType = true;
}
}
/**
* Convert OAS Parameter object to Codegen Parameter object
*
* @param parameter OAS parameter object
* @param imports set of imports for library/package/module
* @return Codegen Parameter object
*/
public CodegenParameter fromParameter(Parameter parameter, Set imports) {
CodegenParameter codegenParameter = CodegenModelFactory.newInstance(CodegenModelType.PARAMETER);
codegenParameter.baseName = parameter.getName();
codegenParameter.description = escapeText(parameter.getDescription());
codegenParameter.unescapedDescription = parameter.getDescription();
if (parameter.getRequired() != null) {
codegenParameter.required = parameter.getRequired();
}
if (parameter.getDeprecated() != null) {
codegenParameter.isDeprecated = parameter.getDeprecated();
}
codegenParameter.jsonSchema = Json.pretty(parameter);
if (GlobalSettings.getProperty("debugParser") != null) {
LOGGER.info("working on Parameter {}", parameter.getName());
LOGGER.info("JSON schema: {}", codegenParameter.jsonSchema);
}
if (parameter.getExtensions() != null && !parameter.getExtensions().isEmpty()) {
codegenParameter.vendorExtensions.putAll(parameter.getExtensions());
}
if (parameter.getSchema() != null && parameter.getSchema().getExtensions() != null && !parameter.getSchema().getExtensions().isEmpty()) {
codegenParameter.vendorExtensions.putAll(parameter.getSchema().getExtensions());
}
Schema parameterSchema;
// the parameter model name is obtained from the schema $ref
// e.g. #/components/schemas/list_pageQuery_parameter => toModelName(list_pageQuery_parameter)
String parameterModelName = null;
if (parameter.getSchema() != null) {
parameterSchema = unaliasSchema(parameter.getSchema());
parameterModelName = getParameterDataType(parameter, parameterSchema);
CodegenProperty prop;
if (this instanceof RustServerCodegen) {
// for rust server, we need to do somethings special as it uses
// $ref (e.g. #components/schemas/Pet) to determine whether it's a model
prop = fromProperty(parameter.getName(), parameterSchema, false);
} else if (getUseInlineModelResolver()) {
prop = fromProperty(parameter.getName(), getReferencedSchemaWhenNotEnum(parameterSchema), false);
} else {
prop = fromProperty(parameter.getName(), parameterSchema, false);
}
codegenParameter.setSchema(prop);
} else if (parameter.getContent() != null) {
Content content = parameter.getContent();
if (content.size() > 1) {
once(LOGGER).warn("Multiple schemas found in content, returning only the first one");
}
Entry entry = content.entrySet().iterator().next();
codegenParameter.contentType = entry.getKey();
parameterSchema = entry.getValue().getSchema();
parameterModelName = getParameterDataType(parameter, parameterSchema);
} else {
parameterSchema = null;
}
if (parameter instanceof QueryParameter || "query".equalsIgnoreCase(parameter.getIn())) {
codegenParameter.isQueryParam = true;
codegenParameter.isAllowEmptyValue = parameter.getAllowEmptyValue() != null && parameter.getAllowEmptyValue();
} else if (parameter instanceof PathParameter || "path".equalsIgnoreCase(parameter.getIn())) {
codegenParameter.required = true;
codegenParameter.isPathParam = true;
} else if (parameter instanceof HeaderParameter || "header".equalsIgnoreCase(parameter.getIn())) {
codegenParameter.isHeaderParam = true;
} else if (parameter instanceof CookieParameter || "cookie".equalsIgnoreCase(parameter.getIn())) {
codegenParameter.isCookieParam = true;
} else {
LOGGER.warn("Unknown parameter type: {}", parameter.getName());
}
if (parameterSchema == null) {
LOGGER.error("Not handling {} as Body Parameter at the moment", parameter);
finishUpdatingParameter(codegenParameter, parameter);
return codegenParameter;
}
// TODO need to review replacing empty map with schemaMapping instead
parameterSchema = unaliasSchema(parameterSchema);
if (parameterSchema == null) {
LOGGER.warn("warning! Schema not found for parameter \" {} \"", parameter.getName());
finishUpdatingParameter(codegenParameter, parameter);
return codegenParameter;
}
if (getUseInlineModelResolver() && !(this instanceof RustServerCodegen)) {
// for rust server, we cannot run the following as it uses
// $ref (e.g. #components/schemas/Pet) to determine whether it's a model
parameterSchema = getReferencedSchemaWhenNotEnum(parameterSchema);
}
ModelUtils.syncValidationProperties(parameterSchema, codegenParameter);
codegenParameter.setTypeProperties(parameterSchema, openAPI);
codegenParameter.setComposedSchemas(getComposedSchemas(parameterSchema));
if (Boolean.TRUE.equals(parameterSchema.getNullable())) { // use nullable defined in the spec
codegenParameter.isNullable = true;
}
if (parameter.getStyle() != null) {
codegenParameter.style = parameter.getStyle().toString();
codegenParameter.isDeepObject = Parameter.StyleEnum.DEEPOBJECT == parameter.getStyle();
codegenParameter.isMatrix = Parameter.StyleEnum.MATRIX == parameter.getStyle();
}
// the default value is false
// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#user-content-parameterexplode
codegenParameter.isExplode = parameter.getExplode() != null && parameter.getExplode();
// TODO revise collectionFormat, default collection format in OAS 3 appears to multi at least for query parameters
// https://swagger.io/docs/specification/serialization/
String collectionFormat = null;
if (ModelUtils.isFileSchema(parameterSchema) && !ModelUtils.isStringSchema(parameterSchema)) {
// swagger v2 only, type file
codegenParameter.isFile = true;
} else if (ModelUtils.isStringSchema(parameterSchema)) {
updateParameterForString(codegenParameter, parameterSchema);
} else if (ModelUtils.isBooleanSchema(parameterSchema)) {
codegenParameter.isPrimitiveType = true;
} else if (ModelUtils.isNumberSchema(parameterSchema)) {
codegenParameter.isPrimitiveType = true;
if (ModelUtils.isFloatSchema(parameterSchema)) { // float
codegenParameter.isFloat = true;
} else if (ModelUtils.isDoubleSchema(parameterSchema)) { // double
codegenParameter.isDouble = true;
}
} else if (ModelUtils.isIntegerSchema(parameterSchema)) { // integer type
codegenParameter.isPrimitiveType = true;
if (ModelUtils.isLongSchema(parameterSchema)) { // int64/long format
codegenParameter.isLong = true;
} else {
codegenParameter.isInteger = true;
if (ModelUtils.isShortSchema(parameterSchema)) { // int32/short format
codegenParameter.isShort = true;
} else { // unbounded integer
}
}
} else if (ModelUtils.isTypeObjectSchema(parameterSchema)) {
if (ModelUtils.isMapSchema(parameterSchema)) { // for map parameter
updateParameterForMap(codegenParameter, parameterSchema, imports);
}
if (ModelUtils.isFreeFormObject(parameterSchema, openAPI)) {
codegenParameter.isFreeFormObject = true;
}
addVarsRequiredVarsAdditionalProps(parameterSchema, codegenParameter);
} else if (ModelUtils.isNullType(parameterSchema)) {
} else if (ModelUtils.isAnyType(parameterSchema)) {
// any schema with no type set, composed schemas often do this
if (ModelUtils.isMapSchema(parameterSchema)) { // for map parameter
updateParameterForMap(codegenParameter, parameterSchema, imports);
}
addVarsRequiredVarsAdditionalProps(parameterSchema, codegenParameter);
} else if (ModelUtils.isArraySchema(parameterSchema)) {
Schema inner = ModelUtils.getSchemaItems(parameterSchema);
collectionFormat = getCollectionFormat(parameter);
// default to csv:
collectionFormat = StringUtils.isEmpty(collectionFormat) ? "csv" : collectionFormat;
CodegenProperty itemsProperty = fromProperty("inner", inner, false);
codegenParameter.items = itemsProperty;
codegenParameter.mostInnerItems = itemsProperty.mostInnerItems;
codegenParameter.baseType = itemsProperty.dataType;
codegenParameter.isContainer = true;
// recursively add import
while (itemsProperty != null) {
imports.add(itemsProperty.baseType);
itemsProperty = itemsProperty.items;
}
} else {
// referenced schemas
}
CodegenProperty codegenProperty = fromProperty(parameter.getName(), parameterSchema, false);
if (Boolean.TRUE.equals(codegenProperty.isModel)) {
codegenParameter.isModel = true;
}
if (parameterModelName != null) {
codegenParameter.dataType = parameterModelName;
if (ModelUtils.isObjectSchema(parameterSchema) || ModelUtils.isComposedSchema(parameterSchema)) {
codegenProperty.complexType = codegenParameter.dataType;
}
} else {
codegenParameter.dataType = codegenProperty.dataType;
}
if (ModelUtils.isArraySchema(parameterSchema)) {
imports.add(codegenProperty.baseType);
}
codegenParameter.dataFormat = codegenProperty.dataFormat;
if (parameter.getRequired() != null) {
codegenParameter.required = parameter.getRequired().booleanValue();
}
// set containerType
codegenParameter.containerType = codegenProperty.containerType;
codegenParameter.containerTypeMapped = codegenProperty.containerTypeMapped;
// enum
updateCodegenPropertyEnum(codegenProperty);
codegenParameter.isEnum = codegenProperty.isEnum;
codegenParameter.isEnumRef = codegenProperty.isEnumRef;
codegenParameter._enum = codegenProperty._enum;
codegenParameter.allowableValues = codegenProperty.allowableValues;
if (codegenProperty.isEnum) {
codegenParameter.datatypeWithEnum = codegenProperty.datatypeWithEnum;
codegenParameter.enumName = codegenProperty.enumName;
if (codegenProperty.defaultValue != null) {
codegenParameter.enumDefaultValue = codegenProperty.defaultValue.replace(codegenProperty.enumName + ".", "");
}
}
if (codegenProperty.items != null && codegenProperty.items.isEnum) {
codegenParameter.datatypeWithEnum = codegenProperty.datatypeWithEnum;
codegenParameter.enumName = codegenProperty.enumName;
codegenParameter.items = codegenProperty.items;
codegenParameter.mostInnerItems = codegenProperty.mostInnerItems;
}
codegenParameter.collectionFormat = collectionFormat;
if ("multi".equals(collectionFormat)) {
codegenParameter.isCollectionFormatMulti = true;
}
codegenParameter.paramName = toParamName(parameter.getName());
codegenParameter.nameInCamelCase = camelize(codegenParameter.paramName, LOWERCASE_FIRST_LETTER);
codegenParameter.nameInPascalCase = camelize(codegenParameter.paramName);
codegenParameter.nameInSnakeCase = CaseFormat.UPPER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, codegenParameter.nameInPascalCase);
codegenParameter.nameInLowerCase = codegenParameter.paramName.toLowerCase(Locale.ROOT);
// import
if (codegenProperty.complexType != null) {
imports.add(codegenProperty.complexType);
}
codegenParameter.pattern = toRegularExpression(parameterSchema.getPattern());
if (codegenParameter.isQueryParam && codegenParameter.isDeepObject && loadDeepObjectIntoItems) {
Schema schema = parameterSchema;
if (schema.get$ref() != null) {
schema = ModelUtils.getReferencedSchema(openAPI, schema);
}
codegenParameter.items = fromProperty(codegenParameter.paramName, schema, false);
// https://swagger.io/docs/specification/serialization/
if (schema != null) {
Map> properties = schema.getProperties();
List requiredVarNames = new ArrayList<>();
if (schema.getRequired() != null) {
requiredVarNames.addAll(schema.getRequired());
}
if (properties != null) {
codegenParameter.items.vars =
properties.entrySet().stream()
.map(entry -> {
CodegenProperty property = fromProperty(entry.getKey(), entry.getValue(), requiredVarNames.contains(entry.getKey()));
return property;
}).collect(Collectors.toList());
} else {
//LOGGER.error("properties is null: {}", schema);
}
} else {
LOGGER.warn(
"No object schema found for deepObject parameter{} deepObject won't have specific properties",
codegenParameter);
}
}
// set default value
codegenParameter.defaultValue = toDefaultParameterValue(codegenProperty, parameterSchema);
finishUpdatingParameter(codegenParameter, parameter);
return codegenParameter;
}
private Schema getReferencedSchemaWhenNotEnum(Schema parameterSchema) {
Schema referencedSchema = ModelUtils.getReferencedSchema(openAPI, parameterSchema);
if (referencedSchema.getEnum() != null && !referencedSchema.getEnum().isEmpty()) {
referencedSchema = parameterSchema;
}
return referencedSchema;
}
/**
* Returns the data type of parameter.
* Returns null by default to use the CodegenProperty.datatype value
*
* @param parameter Parameter
* @param schema Schema
* @return data type
*/
protected String getParameterDataType(Parameter parameter, Schema schema) {
Schema unaliasSchema = unaliasSchema(schema);
if (unaliasSchema.get$ref() != null) {
return toModelName(ModelUtils.getSimpleRef(unaliasSchema.get$ref()));
}
return null;
}
// TODO revise below as it should be replaced by ModelUtils.isByteArraySchema(parameterSchema)
public boolean isDataTypeBinary(String dataType) {
if (dataType != null) {
return dataType.toLowerCase(Locale.ROOT).startsWith("byte");
} else {
return false;
}
}
// TODO revise below as it should be replaced by ModelUtils.isFileSchema(parameterSchema)
public boolean isDataTypeFile(String dataType) {
if (dataType != null) {
return dataType.toLowerCase(Locale.ROOT).equals("file");
} else {
return false;
}
}
/**
* Convert map of OAS SecurityScheme objects to a list of Codegen Security objects
*
* @param securitySchemeMap a map of OAS SecuritySchemeDefinition object
* @return a list of Codegen Security objects
*/
@Override
@SuppressWarnings("static-method")
public List fromSecurity(Map securitySchemeMap) {
if (securitySchemeMap == null) {
return Collections.emptyList();
}
List codegenSecurities = new ArrayList<>(securitySchemeMap.size());
for (String key : securitySchemeMap.keySet()) {
final SecurityScheme securityScheme = securitySchemeMap.get(key);
if (SecurityScheme.Type.APIKEY.equals(securityScheme.getType())) {
final CodegenSecurity cs = defaultCodegenSecurity(key, securityScheme);
cs.isBasic = cs.isOAuth = cs.isOpenId = false;
cs.isApiKey = true;
cs.keyParamName = securityScheme.getName();
cs.isKeyInHeader = securityScheme.getIn() == SecurityScheme.In.HEADER;
cs.isKeyInQuery = securityScheme.getIn() == SecurityScheme.In.QUERY;
cs.isKeyInCookie = securityScheme.getIn() == SecurityScheme.In.COOKIE; //it assumes a validation step prior to generation. (cookie-auth supported from OpenAPI 3.0.0)
codegenSecurities.add(cs);
} else if (SecurityScheme.Type.HTTP.equals(securityScheme.getType())) {
final CodegenSecurity cs = defaultCodegenSecurity(key, securityScheme);
cs.isKeyInHeader = cs.isKeyInQuery = cs.isKeyInCookie = cs.isApiKey = cs.isOAuth = cs.isOpenId = false;
cs.isBasic = true;
if ("basic".equalsIgnoreCase(securityScheme.getScheme())) {
cs.isBasicBasic = true;
} else if ("bearer".equalsIgnoreCase(securityScheme.getScheme())) {
cs.isBasicBearer = true;
cs.bearerFormat = securityScheme.getBearerFormat();
} else if ("signature".equalsIgnoreCase(securityScheme.getScheme())) {
// HTTP signature as defined in https://datatracker.ietf.org/doc/draft-cavage-http-signatures/
// The registry of security schemes is maintained by IANA.
// https://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml
// As of January 2020, the "signature" scheme has not been registered with IANA yet.
// This scheme may have to be changed when it is officially registered with IANA.
cs.isHttpSignature = true;
once(LOGGER).warn("Security scheme 'HTTP signature' is a draft IETF RFC and subject to change.");
} else {
once(LOGGER).warn("Unknown scheme `{}` found in the HTTP security definition.", securityScheme.getScheme());
}
codegenSecurities.add(cs);
} else if (SecurityScheme.Type.OAUTH2.equals(securityScheme.getType())) {
final OAuthFlows flows = securityScheme.getFlows();
boolean isFlowEmpty = true;
if (securityScheme.getFlows() == null) {
throw new RuntimeException("missing oauth flow in " + key);
}
if (flows.getPassword() != null) {
final CodegenSecurity cs = defaultOauthCodegenSecurity(key, securityScheme);
setOauth2Info(cs, flows.getPassword());
cs.isPassword = true;
cs.flow = "password";
codegenSecurities.add(cs);
isFlowEmpty = false;
}
if (flows.getImplicit() != null) {
final CodegenSecurity cs = defaultOauthCodegenSecurity(key, securityScheme);
setOauth2Info(cs, flows.getImplicit());
cs.isImplicit = true;
cs.flow = "implicit";
codegenSecurities.add(cs);
isFlowEmpty = false;
}
if (flows.getClientCredentials() != null) {
final CodegenSecurity cs = defaultOauthCodegenSecurity(key, securityScheme);
setOauth2Info(cs, flows.getClientCredentials());
cs.isApplication = true;
cs.flow = "application";
codegenSecurities.add(cs);
isFlowEmpty = false;
}
if (flows.getAuthorizationCode() != null) {
final CodegenSecurity cs = defaultOauthCodegenSecurity(key, securityScheme);
setOauth2Info(cs, flows.getAuthorizationCode());
cs.isCode = true;
cs.flow = "accessCode";
codegenSecurities.add(cs);
isFlowEmpty = false;
}
if (isFlowEmpty) {
once(LOGGER).error("Invalid flow definition defined in the security scheme: {}", flows);
}
} else if (SecurityScheme.Type.OPENIDCONNECT.equals(securityScheme.getType())) {
final CodegenSecurity cs = defaultCodegenSecurity(key, securityScheme);
cs.isKeyInHeader = cs.isKeyInQuery = cs.isKeyInCookie = cs.isApiKey = cs.isBasic = false;
cs.isOpenId = true;
cs.openIdConnectUrl = securityScheme.getOpenIdConnectUrl();
if (securityScheme.getFlows() != null) {
setOpenIdConnectInfo(cs, securityScheme.getFlows().getAuthorizationCode());
}
codegenSecurities.add(cs);
} else {
once(LOGGER).error("Unknown type `{}` found in the security definition `{}`.", securityScheme.getType(), securityScheme.getName());
}
}
return codegenSecurities;
}
private CodegenSecurity defaultCodegenSecurity(String key, SecurityScheme securityScheme) {
final CodegenSecurity cs = CodegenModelFactory.newInstance(CodegenModelType.SECURITY);
cs.name = key;
cs.description = securityScheme.getDescription();
cs.type = securityScheme.getType().toString();
cs.isCode = cs.isPassword = cs.isApplication = cs.isImplicit = cs.isOpenId = false;
cs.isHttpSignature = false;
cs.isBasicBasic = cs.isBasicBearer = false;
cs.scheme = securityScheme.getScheme();
if (securityScheme.getExtensions() != null) {
cs.vendorExtensions.putAll(securityScheme.getExtensions());
}
return cs;
}
private CodegenSecurity defaultOauthCodegenSecurity(String key, SecurityScheme securityScheme) {
final CodegenSecurity cs = defaultCodegenSecurity(key, securityScheme);
cs.isKeyInHeader = cs.isKeyInQuery = cs.isKeyInCookie = cs.isApiKey = cs.isBasic = cs.isOpenId = false;
cs.isOAuth = true;
return cs;
}
protected void setReservedWordsLowerCase(List words) {
reservedWords = new HashSet<>();
for (String word : words) {
reservedWords.add(word.toLowerCase(Locale.ROOT));
}
}
protected boolean isReservedWord(String word) {
return word != null && reservedWords.contains(word.toLowerCase(Locale.ROOT));
}
/**
* Get operationId from the operation object, and if it's blank, generate a new one from the given parameters.
*
* @param operation the operation object
* @param path the path of the operation
* @param httpMethod the HTTP method of the operation
* @return the (generated) operationId
*/
protected String getOrGenerateOperationId(Operation operation, String path, String httpMethod) {
String operationId = operation.getOperationId();
if (StringUtils.isBlank(operationId)) {
String tmpPath = path;
tmpPath = tmpPath.replaceAll("\\{", "");
tmpPath = tmpPath.replaceAll("\\}", "");
String[] parts = (tmpPath + "/" + httpMethod).split("/");
StringBuilder builder = new StringBuilder();
if ("/".equals(tmpPath)) {
// must be root tmpPath
builder.append("root");
}
for (String part : parts) {
if (part.length() > 0) {
if (builder.toString().length() == 0) {
part = Character.toLowerCase(part.charAt(0)) + part.substring(1);
} else {
part = camelize(part);
}
builder.append(part);
}
}
operationId = sanitizeName(builder.toString());
LOGGER.warn("Empty operationId found for path: {} {}. Renamed to auto-generated operationId: {}", httpMethod, path, operationId);
}
// remove prefix in operationId
if (removeOperationIdPrefix) {
// The prefix is everything before the removeOperationIdPrefixCount occurrence of removeOperationIdPrefixDelimiter
String[] components = operationId.split("[" + removeOperationIdPrefixDelimiter + "]");
if (components.length > 1) {
// If removeOperationIdPrefixCount is -1 or bigger that the number of occurrences, uses the last one
int component_number = removeOperationIdPrefixCount == -1 ? components.length - 1 : removeOperationIdPrefixCount;
component_number = Math.min(component_number, components.length - 1);
// Reconstruct the operationId from its split elements and the delimiter
operationId = String.join(removeOperationIdPrefixDelimiter, Arrays.copyOfRange(components, component_number, components.length));
}
}
operationId = removeNonNameElementToCamelCase(operationId);
if (operationIdNameMapping.containsKey(operationId)) {
return operationIdNameMapping.get(operationId);
} else {
return toOperationId(operationId);
}
}
/**
* Check the type to see if it needs import the library/module/package
*
* @param type name of the type
* @return true if the library/module/package of the corresponding type needs to be imported
*/
protected boolean needToImport(String type) {
return StringUtils.isNotBlank(type) && !defaultIncludes.contains(type)
&& !languageSpecificPrimitives.contains(type);
}
@SuppressWarnings("static-method")
protected List> toExamples(Map examples) {
if (examples == null) {
return null;
}
final List