All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openapitools.codegen.DefaultCodegen Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech)
 * Copyright 2018 SmartBear Software
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.openapitools.codegen;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.Ticker;
import com.google.common.base.CaseFormat;
import com.google.common.collect.ImmutableMap;
import com.samskivert.mustache.Mustache;
import com.samskivert.mustache.Mustache.Compiler;
import com.samskivert.mustache.Mustache.Lambda;

import io.swagger.v3.core.util.Json;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.PathItem;
import io.swagger.v3.oas.models.callbacks.Callback;
import io.swagger.v3.oas.models.examples.Example;
import io.swagger.v3.oas.models.headers.Header;
import io.swagger.v3.oas.models.media.*;
import io.swagger.v3.oas.models.parameters.*;
import io.swagger.v3.oas.models.responses.ApiResponse;
import io.swagger.v3.oas.models.responses.ApiResponses;
import io.swagger.v3.oas.models.security.OAuthFlow;
import io.swagger.v3.oas.models.security.OAuthFlows;
import io.swagger.v3.oas.models.security.SecurityScheme;
import io.swagger.v3.oas.models.servers.Server;
import io.swagger.v3.oas.models.servers.ServerVariable;
import io.swagger.v3.parser.util.SchemaTypeUtil;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.openapitools.codegen.CodegenDiscriminator.MappedModel;
import org.openapitools.codegen.api.TemplatingEngineAdapter;
import org.openapitools.codegen.config.GlobalSettings;
import org.openapitools.codegen.examples.ExampleGenerator;
import org.openapitools.codegen.meta.FeatureSet;
import org.openapitools.codegen.meta.GeneratorMetadata;
import org.openapitools.codegen.meta.Stability;
import org.openapitools.codegen.meta.features.*;
import org.openapitools.codegen.serializer.SerializerUtils;
import org.openapitools.codegen.templating.MustacheEngineAdapter;
import org.openapitools.codegen.templating.mustache.CamelCaseLambda;
import org.openapitools.codegen.templating.mustache.IndentedLambda;
import org.openapitools.codegen.templating.mustache.LowercaseLambda;
import org.openapitools.codegen.templating.mustache.TitlecaseLambda;
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.File;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.openapitools.codegen.utils.StringUtils.*;

public class DefaultCodegen implements CodegenConfig {
    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultCodegen.class);

    public static FeatureSet DefaultFeatureSet;

    // A cache of sanitized words. The sanitizeName() method is invoked many times with the same
    // arguments, this cache is used to optimized performance.
    private static Cache sanitizedNameCache;

    static {
        DefaultFeatureSet = FeatureSet.newBuilder()
                .includeDataTypeFeatures(
                        DataTypeFeature.Int32, DataTypeFeature.Int64, DataTypeFeature.Float, DataTypeFeature.Double,
                        DataTypeFeature.Decimal, DataTypeFeature.String, DataTypeFeature.Byte, DataTypeFeature.Binary,
                        DataTypeFeature.Boolean, DataTypeFeature.Date, DataTypeFeature.DateTime, DataTypeFeature.Password,
                        DataTypeFeature.File, DataTypeFeature.Array, DataTypeFeature.Maps, DataTypeFeature.CollectionFormat,
                        DataTypeFeature.CollectionFormatMulti, DataTypeFeature.Enum, DataTypeFeature.ArrayOfEnum, DataTypeFeature.ArrayOfModel,
                        DataTypeFeature.ArrayOfCollectionOfPrimitives, DataTypeFeature.ArrayOfCollectionOfModel, DataTypeFeature.ArrayOfCollectionOfEnum,
                        DataTypeFeature.MapOfEnum, DataTypeFeature.MapOfModel, DataTypeFeature.MapOfCollectionOfPrimitives,
                        DataTypeFeature.MapOfCollectionOfModel, DataTypeFeature.MapOfCollectionOfEnum
                        // Custom types are template specific
                )
                .includeDocumentationFeatures(
                        DocumentationFeature.Api, DocumentationFeature.Model
                        // README is template specific
                )
                .includeGlobalFeatures(
                        GlobalFeature.Host, GlobalFeature.BasePath, GlobalFeature.Info, GlobalFeature.PartialSchemes,
                        GlobalFeature.Consumes, GlobalFeature.Produces, GlobalFeature.ExternalDocumentation, GlobalFeature.Examples,
                        GlobalFeature.Callbacks
                        // TODO: xml structures, styles, link objects, parameterized servers, full schemes for OAS 2.0
                )
                .includeSchemaSupportFeatures(
                        SchemaSupportFeature.Simple, SchemaSupportFeature.Composite,
                        SchemaSupportFeature.Polymorphism
                        // Union (OneOf) not 100% yet.
                )
                .includeParameterFeatures(
                        ParameterFeature.Path, ParameterFeature.Query, ParameterFeature.Header, ParameterFeature.Body,
                        ParameterFeature.FormUnencoded, ParameterFeature.FormMultipart, ParameterFeature.Cookie
                )
                .includeSecurityFeatures(
                        SecurityFeature.BasicAuth, SecurityFeature.ApiKey, SecurityFeature.BearerToken,
                        SecurityFeature.OAuth2_Implicit, SecurityFeature.OAuth2_Password,
                        SecurityFeature.OAuth2_ClientCredentials, SecurityFeature.OAuth2_AuthorizationCode
                        // OpenIDConnect not yet supported
                )
                .includeWireFormatFeatures(
                        WireFormatFeature.JSON, WireFormatFeature.XML
                        // PROTOBUF and Custom are generator specific
                )
                .build();

        sanitizedNameCache = Caffeine.newBuilder()
                .maximumSize(500)
                .expireAfterAccess(10, TimeUnit.SECONDS)
                .ticker(Ticker.systemTicker())
                .build();
    }

    protected GeneratorMetadata generatorMetadata;
    protected String inputSpec;
    protected String outputFolder = "";
    protected Set defaultIncludes = new HashSet();
    protected Map typeMapping = new HashMap();
    protected Map instantiationTypes = new HashMap();
    protected Set reservedWords = new HashSet();
    protected Set languageSpecificPrimitives = new HashSet();
    protected Map importMapping = new HashMap();
    protected String modelPackage = "", apiPackage = "", fileSuffix;
    protected String modelNamePrefix = "", modelNameSuffix = "";
    protected String apiNameSuffix = "Api";
    protected String testPackage = "";
    /*
    apiTemplateFiles are for API outputs only (controllers/handlers).
    API templates may be written multiple times; APIs are grouped by tag and the file is written once per tag group.
    */
    protected Map apiTemplateFiles = new HashMap();
    protected Map modelTemplateFiles = new HashMap();
    protected Map apiTestTemplateFiles = new HashMap();
    protected Map modelTestTemplateFiles = new HashMap();
    protected Map apiDocTemplateFiles = new HashMap();
    protected Map modelDocTemplateFiles = new HashMap();
    protected Map reservedWordsMappings = new HashMap();
    protected String templateDir;
    protected String embeddedTemplateDir;
    protected String commonTemplateDir = "_common";
    protected Map additionalProperties = new HashMap();
    protected Map serverVariables = new HashMap();
    protected Map vendorExtensions = new HashMap();
    /*
    Supporting files are those which aren't models, APIs, or docs.
    These get a different map of data bound to the templates. Supporting files are written once.
    See also 'apiTemplateFiles'.
    */
    protected List supportingFiles = new ArrayList();
    protected List cliOptions = new ArrayList();
    protected boolean skipOverwrite;
    protected boolean removeOperationIdPrefix;
    protected boolean supportsMultipleInheritance;
    protected boolean supportsInheritance;
    protected boolean supportsMixins;
    protected Map supportedLibraries = new LinkedHashMap();
    protected String library;
    protected Boolean sortParamsByRequiredFlag = true;
    protected Boolean sortModelPropertiesByRequiredFlag = false;
    protected Boolean ensureUniqueParams = true;
    protected Boolean allowUnicodeIdentifiers = false;
    protected String gitHost, gitUserId, gitRepoId, releaseNote;
    protected String httpUserAgent;
    protected Boolean hideGenerationTimestamp = true;
    // How to encode special characters like $
    // They are translated to words like "Dollar" and prefixed with '
    // Then translated back during JSON encoding and decoding
    protected Map specialCharReplacements = new HashMap();
    // When a model is an alias for a simple type
    protected Map typeAliases = null;
    protected Boolean prependFormOrBodyParameters = false;
    // The extension of the generated documentation files (defaults to markdown .md)
    protected String docExtension;
    protected String ignoreFilePathOverride;
    // flag to indicate whether to use environment variable to post process file
    protected boolean enablePostProcessFile = false;
    private TemplatingEngineAdapter templatingEngine = new MustacheEngineAdapter();
    // flag to indicate whether to use the utils.OneOfImplementorAdditionalData related logic
    protected boolean useOneOfInterfaces = false;
    // whether or not the oneOf imports machinery should add oneOf interfaces as imports in implementing classes
    protected boolean addOneOfInterfaceImports = false;
    protected List addOneOfInterfaces = new ArrayList();

    // flag to indicate whether to only update files whose contents have changed
    protected boolean enableMinimalUpdate = false;

    // acts strictly upon a spec, potentially modifying it to have consistent behavior across generators.
    protected boolean strictSpecBehavior = true;
    // flag to indicate whether enum value prefixes are removed
    protected boolean removeEnumValuePrefix = true;

    // make openapi available to all methods
    protected OpenAPI openAPI;

    public List cliOptions() {
        return cliOptions;
    }

    public void processOpts() {
        if (additionalProperties.containsKey(CodegenConstants.TEMPLATE_DIR)) {
            this.setTemplateDir((String) additionalProperties.get(CodegenConstants.TEMPLATE_DIR));
        }

        if (additionalProperties.containsKey(CodegenConstants.MODEL_PACKAGE)) {
            this.setModelPackage((String) additionalProperties.get(CodegenConstants.MODEL_PACKAGE));
        }

        if (additionalProperties.containsKey(CodegenConstants.API_PACKAGE)) {
            this.setApiPackage((String) additionalProperties.get(CodegenConstants.API_PACKAGE));
        }

        if (additionalProperties.containsKey(CodegenConstants.HIDE_GENERATION_TIMESTAMP)) {
            setHideGenerationTimestamp(convertPropertyToBooleanAndWriteBack(CodegenConstants.HIDE_GENERATION_TIMESTAMP));
        } else {
            additionalProperties.put(CodegenConstants.HIDE_GENERATION_TIMESTAMP, hideGenerationTimestamp);
        }

        if (additionalProperties.containsKey(CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG)) {
            this.setSortParamsByRequiredFlag(Boolean.valueOf(additionalProperties
                    .get(CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG).toString()));
        }

        if (additionalProperties.containsKey(CodegenConstants.SORT_MODEL_PROPERTIES_BY_REQUIRED_FLAG)) {
            this.setSortModelPropertiesByRequiredFlag(Boolean.valueOf(additionalProperties
                    .get(CodegenConstants.SORT_MODEL_PROPERTIES_BY_REQUIRED_FLAG).toString()));
        }

        if (additionalProperties.containsKey(CodegenConstants.PREPEND_FORM_OR_BODY_PARAMETERS)) {
            this.setPrependFormOrBodyParameters(Boolean.valueOf(additionalProperties
                    .get(CodegenConstants.PREPEND_FORM_OR_BODY_PARAMETERS).toString()));
        }

        if (additionalProperties.containsKey(CodegenConstants.ENSURE_UNIQUE_PARAMS)) {
            this.setEnsureUniqueParams(Boolean.valueOf(additionalProperties
                    .get(CodegenConstants.ENSURE_UNIQUE_PARAMS).toString()));
        }

        if (additionalProperties.containsKey(CodegenConstants.ALLOW_UNICODE_IDENTIFIERS)) {
            this.setAllowUnicodeIdentifiers(Boolean.valueOf(additionalProperties
                    .get(CodegenConstants.ALLOW_UNICODE_IDENTIFIERS).toString()));
        }

        if (additionalProperties.containsKey(CodegenConstants.API_NAME_SUFFIX)) {
            this.setApiNameSuffix((String) additionalProperties.get(CodegenConstants.API_NAME_SUFFIX));
        }

        if (additionalProperties.containsKey(CodegenConstants.MODEL_NAME_PREFIX)) {
            this.setModelNamePrefix((String) additionalProperties.get(CodegenConstants.MODEL_NAME_PREFIX));
        }

        if (additionalProperties.containsKey(CodegenConstants.MODEL_NAME_SUFFIX)) {
            this.setModelNameSuffix((String) additionalProperties.get(CodegenConstants.MODEL_NAME_SUFFIX));
        }

        if (additionalProperties.containsKey(CodegenConstants.REMOVE_OPERATION_ID_PREFIX)) {
            this.setRemoveOperationIdPrefix(Boolean.valueOf(additionalProperties
                    .get(CodegenConstants.REMOVE_OPERATION_ID_PREFIX).toString()));
        }

        if (additionalProperties.containsKey(CodegenConstants.DOCEXTENSION)) {
            this.setDocExtension(String.valueOf(additionalProperties
                    .get(CodegenConstants.DOCEXTENSION).toString()));
        }

        if (additionalProperties.containsKey(CodegenConstants.ENABLE_POST_PROCESS_FILE)) {
            this.setEnablePostProcessFile(Boolean.valueOf(additionalProperties
                    .get(CodegenConstants.ENABLE_POST_PROCESS_FILE).toString()));
        }

        if (additionalProperties.containsKey(CodegenConstants.GENERATE_ALIAS_AS_MODEL)) {
            ModelUtils.setGenerateAliasAsModel(Boolean.valueOf(additionalProperties
                    .get(CodegenConstants.GENERATE_ALIAS_AS_MODEL).toString()));
        }

        if (additionalProperties.containsKey(CodegenConstants.REMOVE_ENUM_VALUE_PREFIX)) {
            this.setRemoveEnumValuePrefix(Boolean.valueOf(additionalProperties
                    .get(CodegenConstants.REMOVE_ENUM_VALUE_PREFIX).toString()));
        }
    }

    /***
     * Preset map builder with commonly used Mustache lambdas.
     *
     * To extend the map, override addMustacheLambdas(), call parent method
     * first and then add additional lambdas to the returned builder.
     *
     * If common lambdas are not desired, override addMustacheLambdas() method
     * and return empty builder.
     *
     * @return preinitialized map builder with common lambdas
     */
    protected ImmutableMap.Builder addMustacheLambdas() {

        return new ImmutableMap.Builder()
                .put("lowercase", new LowercaseLambda().generator(this))
                .put("uppercase", new UppercaseLambda())
                .put("titlecase", new TitlecaseLambda())
                .put("camelcase", new CamelCaseLambda().generator(this))
                .put("indented", new IndentedLambda())
                .put("indented_8", new IndentedLambda(8, " "))
                .put("indented_12", new IndentedLambda(12, " "))
                .put("indented_16", new IndentedLambda(16, " "));
    }

    private void registerMustacheLambdas() {
        ImmutableMap lambdas = addMustacheLambdas().build();

        if (lambdas.size() == 0) {
            return;
        }

        if (additionalProperties.containsKey("lambda")) {
            LOGGER.error("A property called 'lambda' already exists in additionalProperties");
            throw new RuntimeException("A property called 'lambda' already exists in additionalProperties");
        }
        additionalProperties.put("lambda", lambdas);
    }

    // override with any special post-processing for all models
    @SuppressWarnings({"static-method", "unchecked"})
    public Map postProcessAllModels(Map objs) {
        if (this.useOneOfInterfaces) {
            // First, add newly created oneOf interfaces
            for (CodegenModel cm : addOneOfInterfaces) {
                Map modelValue = new HashMap() {{
                    putAll(additionalProperties());
                    put("model", cm);
                }};
                List modelsValue = Arrays.asList(modelValue);
                List> importsValue = new ArrayList>();
                Map objsValue = new HashMap() {{
                    put("models", modelsValue);
                    put("package", modelPackage());
                    put("imports", importsValue);
                    put("classname", cm.classname);
                    putAll(additionalProperties);
                }};
                objs.put(cm.name, objsValue);
            }

            // Gather data from all the models that contain oneOf into OneOfImplementorAdditionalData classes
            // (see docstring of that class to find out what information is gathered and why)
            Map additionalDataMap = new HashMap();
            for (Map.Entry modelsEntry : objs.entrySet()) {
                Map modelsAttrs = (Map) modelsEntry.getValue();
                List models = (List) modelsAttrs.get("models");
                List> modelsImports = (List>) modelsAttrs.getOrDefault("imports", new ArrayList>());
                for (Object _mo : models) {
                    Map mo = (Map) _mo;
                    CodegenModel cm = (CodegenModel) mo.get("model");
                    if (cm.oneOf.size() > 0) {
                        cm.vendorExtensions.put("x-is-one-of-interface", true);
                        for (String one : cm.oneOf) {
                            if (!additionalDataMap.containsKey(one)) {
                                additionalDataMap.put(one, new OneOfImplementorAdditionalData(one));
                            }
                            additionalDataMap.get(one).addFromInterfaceModel(cm, modelsImports);
                        }
                        // if this is oneOf interface, make sure we include the necessary imports for it
                        addImportsToOneOfInterface(modelsImports);
                    }
                }
            }

            // Add all the data from OneOfImplementorAdditionalData classes to the implementing models
            for (Map.Entry modelsEntry : objs.entrySet()) {
                Map modelsAttrs = (Map) modelsEntry.getValue();
                List models = (List) modelsAttrs.get("models");
                List> imports = (List>) modelsAttrs.get("imports");
                for (Object _implmo : models) {
                    Map implmo = (Map) _implmo;
                    CodegenModel implcm = (CodegenModel) implmo.get("model");
                    String modelName = toModelName(implcm.name);
                    if (additionalDataMap.containsKey(modelName)) {
                        additionalDataMap.get(modelName).addToImplementor(this, implcm, imports, addOneOfInterfaceImports);
                    }
                }
            }
        }

        return objs;
    }

    /**
     * Index all CodegenModels by model name.
     *
     * @param objs Map of models
     * @return map of all models indexed by names
     */
    public Map getAllModels(Map objs) {
        Map allModels = new HashMap();
        for (Entry entry : objs.entrySet()) {
            String modelName = toModelName(entry.getKey());
            Map inner = (Map) entry.getValue();
            List> models = (List>) inner.get("models");
            for (Map mo : models) {
                CodegenModel cm = (CodegenModel) mo.get("model");
                allModels.put(modelName, cm);
            }
        }
        return allModels;
    }

    /**
     * Loop through all models to update different flags (e.g. isSelfReference), children models, etc
     *
     * @param objs Map of models
     * @return maps of models with various updates
     */
    public Map updateAllModels(Map objs) {
        Map allModels = getAllModels(objs);

        // Fix up all parent and interface CodegenModel references.
        for (CodegenModel cm : allModels.values()) {
            if (cm.getParent() != null) {
                cm.setParentModel(allModels.get(cm.getParent()));
            }
            if (cm.getInterfaces() != null && !cm.getInterfaces().isEmpty()) {
                cm.setInterfaceModels(new ArrayList(cm.getInterfaces().size()));
                for (String intf : cm.getInterfaces()) {
                    CodegenModel intfModel = allModels.get(intf);
                    if (intfModel != null) {
                        cm.getInterfaceModels().add(intfModel);
                    }
                }
            }
        }

        // Let parent know about all its children
        for (String name : allModels.keySet()) {
            CodegenModel cm = allModels.get(name);
            CodegenModel parent = allModels.get(cm.getParent());
            // if a discriminator exists on the parent, don't add this child to the inheritance hierarchy
            // TODO Determine what to do if the parent discriminator name == the grandparent discriminator name
            while (parent != null) {
                if (parent.getChildren() == null) {
                    parent.setChildren(new ArrayList());
                }
                parent.getChildren().add(cm);
                parent.hasChildren = true;
                if (parent.getDiscriminator() == null) {
                    parent = allModels.get(parent.getParent());
                } else {
                    parent = null;
                }
            }
        }

        // loop through properties of each model to detect self-reference
        for (Map.Entry entry : objs.entrySet()) {
            Map inner = (Map) entry.getValue();
            List> models = (List>) inner.get("models");
            for (Map mo : models) {
                CodegenModel cm = (CodegenModel) mo.get("model");
                for (CodegenProperty cp : cm.allVars) {
                    // detect self import
                    if (cp.dataType.equalsIgnoreCase(cm.classname) ||
                            (cp.isContainer && cp.items != null && cp.items.dataType.equalsIgnoreCase(cm.classname))) {
                        cm.imports.remove(cm.classname); // remove self import
                        cp.isSelfReference = true;
                    }
                }
            }
        }
        setCircularReferences(allModels);

        return objs;
    }

    public void setCircularReferences(Map models) {
        final Map> dependencyMap = models.entrySet().stream()
            .collect(Collectors.toMap(Entry::getKey, entry -> getModelDependencies(entry.getValue())));

        models.keySet().forEach(name -> setCircularReferencesOnProperties(name, dependencyMap));
    }

    private List getModelDependencies(CodegenModel model) {
        return model.getAllVars().stream()
            .map(prop -> {
                if (prop.isContainer) {
                    return prop.items.dataType == null ? null : prop;
                }
                return prop.dataType == null ? null : prop;
            })
            .filter(prop -> prop != null)
            .collect(Collectors.toList());
    }

    private void setCircularReferencesOnProperties(final String root,
                                                   final Map> dependencyMap) {
        dependencyMap.getOrDefault(root, new ArrayList<>()).stream()
            .forEach(prop -> {
                final List unvisited =
                    Collections.singletonList(prop.isContainer ? prop.items.dataType : prop.dataType);
                prop.isCircularReference = isCircularReference(root,
                                                               new HashSet<>(),
                                                               new ArrayList<>(unvisited),
                                                               dependencyMap);
            });
    }

    private boolean isCircularReference(final String root,
                                        final Set visited,
                                        final List unvisited,
                                        final Map> dependencyMap) {
        for (int i = 0; i < unvisited.size(); i++) {
            final String next = unvisited.get(i);
            if (!visited.contains(next)) {
                if (next.equals(root)) {
                    return true;
                }
                dependencyMap.getOrDefault(next, new ArrayList<>())
                    .forEach(prop -> unvisited.add(prop.isContainer ? prop.items.dataType : prop.dataType));
                visited.add(next);
            }
        }
        return false;
    }

    // override with any special post-processing
    @SuppressWarnings("static-method")
    public Map postProcessModels(Map objs) {
        return objs;
    }

    /**
     * post process enum defined in model's properties
     *
     * @param objs Map of models
     * @return maps of models with better enum support
     */
    public Map postProcessModelsEnum(Map objs) {
        List models = (List) objs.get("models");
        for (Object _mo : models) {
            Map mo = (Map) _mo;
            CodegenModel cm = (CodegenModel) mo.get("model");

            // for enum model
            if (Boolean.TRUE.equals(cm.isEnum) && cm.allowableValues != null) {
                Map allowableValues = cm.allowableValues;
                List values = (List) allowableValues.get("values");
                List> enumVars = buildEnumVars(values, cm.dataType);
                // if "x-enum-varnames" or "x-enum-descriptions" defined, update varnames
                updateEnumVarsWithExtensions(enumVars, cm.getVendorExtensions());
                cm.allowableValues.put("enumVars", enumVars);
            }

            // update codegen property enum with proper naming convention
            // and handling of numbers, special characters
            for (CodegenProperty var : cm.vars) {
                updateCodegenPropertyEnum(var);
            }

            for (CodegenProperty var : cm.allVars) {
                updateCodegenPropertyEnum(var);
            }

            for (CodegenProperty var : cm.requiredVars) {
                updateCodegenPropertyEnum(var);
            }

            for (CodegenProperty var : cm.optionalVars) {
                updateCodegenPropertyEnum(var);
            }

            for (CodegenProperty var : cm.parentVars) {
                updateCodegenPropertyEnum(var);
            }

            for (CodegenProperty var : cm.readOnlyVars) {
                updateCodegenPropertyEnum(var);
            }

            for (CodegenProperty var : cm.readWriteVars) {
                updateCodegenPropertyEnum(var);
            }

        }
        return objs;
    }

    /**
     * Returns the common prefix of variables for enum naming if
     * two or more variables are present
     *
     * @param vars List of variable names
     * @return the common prefix for naming
     */
    public String findCommonPrefixOfVars(List vars) {
        if (vars.size() > 1) {
            try {
                String[] listStr = vars.toArray(new String[vars.size()]);
                String prefix = StringUtils.getCommonPrefix(listStr);
                // exclude trailing characters that should be part of a valid variable
                // e.g. ["status-on", "status-off"] => "status-" (not "status-o")
                return prefix.replaceAll("[a-zA-Z0-9]+\\z", "");
            } catch (ArrayStoreException e) {
                // do nothing, just return default value
            }
        }
        return "";
    }

    /**
     * Return the enum default value in the language specified format
     *
     * @param value    enum variable name
     * @param datatype data type
     * @return the default value for the enum
     */
    public String toEnumDefaultValue(String value, String datatype) {
        return datatype + "." + value;
    }

    /**
     * Return the enum value in the language specified format
     * e.g. status becomes "status"
     *
     * @param value    enum variable name
     * @param datatype data type
     * @return the sanitized value for enum
     */
    public String toEnumValue(String value, String datatype) {
        if ("number".equalsIgnoreCase(datatype)) {
            return value;
        } else {
            return "\"" + escapeText(value) + "\"";
        }
    }

    /**
     * Return the sanitized variable name for enum
     *
     * @param value    enum variable name
     * @param datatype data type
     * @return the sanitized variable name for enum
     */
    public String toEnumVarName(String value, String datatype) {
        if (value.length() == 0) {
            return "EMPTY";
        }

        String var = value.replaceAll("\\W+", "_").toUpperCase(Locale.ROOT);
        if (var.matches("\\d.*")) {
            return "_" + var;
        } else {
            return var;
        }
    }

    @Override
    public void setOpenAPI(OpenAPI openAPI) {
        this.openAPI = openAPI;
    }

    // override with any special post-processing
    @SuppressWarnings("static-method")
    public Map postProcessOperationsWithModels(Map objs, List allModels) {
        return objs;
    }

    // override with any special post-processing
    @SuppressWarnings("static-method")
    public Map postProcessSupportingFileData(Map objs) {
        return objs;
    }

    // override to post-process any model properties
    @SuppressWarnings("unused")
    public void postProcessModelProperty(CodegenModel model, CodegenProperty property) {
    }

    // override to post-process any parameters
    @SuppressWarnings("unused")
    public void postProcessParameter(CodegenParameter parameter) {
    }

    //override with any special handling of the entire OpenAPI spec document
    @SuppressWarnings("unused")
    public void preprocessOpenAPI(OpenAPI openAPI) {
        if (useOneOfInterfaces) {
            // we process the openapi schema here to find oneOf schemas and create interface models for them
            Map schemas = new HashMap(openAPI.getComponents().getSchemas());
            if (schemas == null) {
                schemas = new HashMap();
            }
            Map pathItems = openAPI.getPaths();

            // we need to add all request and response bodies to processed schemas
            if (pathItems != null) {
                for (Map.Entry e : pathItems.entrySet()) {
                    for (Map.Entry op : e.getValue().readOperationsMap().entrySet()) {
                        String opId = getOrGenerateOperationId(op.getValue(), e.getKey(), op.getKey().toString());
                        // process request body
                        RequestBody b = ModelUtils.getReferencedRequestBody(openAPI, op.getValue().getRequestBody());
                        Schema requestSchema = null;
                        if (b != null) {
                            requestSchema = ModelUtils.getSchemaFromRequestBody(b);
                        }
                        if (requestSchema != null) {
                            schemas.put(opId, requestSchema);
                        }
                        // process all response bodies
                        for (Map.Entry ar : op.getValue().getResponses().entrySet()) {
                            ApiResponse a = ModelUtils.getReferencedApiResponse(openAPI, ar.getValue());
                            Schema responseSchema = ModelUtils.getSchemaFromResponse(a);
                            if (responseSchema != null) {
                                schemas.put(opId + ar.getKey(), responseSchema);
                            }
                        }
                    }
                }
            }

            // go through all gathered schemas and add them as interfaces to be created
            for (Map.Entry e : schemas.entrySet()) {
                String n = toModelName(e.getKey());
                Schema s = e.getValue();
                String nOneOf = toModelName(n + "OneOf");
                if (ModelUtils.isComposedSchema(s)) {
                    addOneOfNameExtension((ComposedSchema) s, n);
                } else if (ModelUtils.isArraySchema(s)) {
                    Schema items = ((ArraySchema) s).getItems();
                    if (ModelUtils.isComposedSchema(items)) {
                        addOneOfNameExtension((ComposedSchema) items, nOneOf);
                        addOneOfInterfaceModel((ComposedSchema) items, nOneOf);
                    }
                } else if (ModelUtils.isMapSchema(s)) {
                    Schema addProps = ModelUtils.getAdditionalProperties(s);
                    if (addProps != null && ModelUtils.isComposedSchema(addProps)) {
                        addOneOfNameExtension((ComposedSchema) addProps, nOneOf);
                        addOneOfInterfaceModel((ComposedSchema) addProps, nOneOf);
                    }
                }
            }
        }
    }

    // override with any special handling of the entire OpenAPI spec document
    @SuppressWarnings("unused")
    public void processOpenAPI(OpenAPI openAPI) {
    }

    // override with any special handling of the JMustache compiler
    @SuppressWarnings("unused")
    public Compiler processCompiler(Compiler compiler) {
        return compiler;
    }

    // override with any special handling for the templating engine
    @SuppressWarnings("unused")
    public TemplatingEngineAdapter processTemplatingEngine(TemplatingEngineAdapter templatingEngine) {
        return templatingEngine;
    }

    // override with any special text escaping logic
    @SuppressWarnings("static-method")
    public String escapeText(String input) {
        if (input == null) {
            return input;
        }

        // remove \t, \n, \r
        // replace \ with \\
        // replace " with \"
        // outter unescape to retain the original multi-byte characters
        // finally escalate characters avoiding code injection
        return escapeUnsafeCharacters(
                StringEscapeUtils.unescapeJava(
                        StringEscapeUtils.escapeJava(input)
                                .replace("\\/", "/"))
                        .replaceAll("[\\t\\n\\r]", " ")
                        .replace("\\", "\\\\")
                        .replace("\"", "\\\""));
    }

    /**
     * Escape characters while allowing new lines
     *
     * @param input String to be escaped
     * @return escaped string
     */
    public String escapeTextWhileAllowingNewLines(String input) {
        if (input == null) {
            return input;
        }

        // remove \t
        // replace \ with \\
        // replace " with \"
        // outter unescape to retain the original multi-byte characters
        // finally escalate characters avoiding code injection
        return escapeUnsafeCharacters(
                StringEscapeUtils.unescapeJava(
                        StringEscapeUtils.escapeJava(input)
                                .replace("\\/", "/"))
                        .replaceAll("[\\t]", " ")
                        .replace("\\", "\\\\")
                        .replace("\"", "\\\""));
    }

    // override with any special encoding and escaping logic
    @SuppressWarnings("static-method")
    public String encodePath(String input) {
        return escapeText(input);
    }

    /**
     * override with any special text escaping logic to handle unsafe
     * characters so as to avoid code injection
     *
     * @param input String to be cleaned up
     * @return string with unsafe characters removed or escaped
     */
    public String escapeUnsafeCharacters(String input) {
        LOGGER.warn("escapeUnsafeCharacters should be overridden in the code generator with proper logic to escape " +
                "unsafe characters");
        // doing nothing by default and code generator should implement
        // the logic to prevent code injection
        // later we'll make this method abstract to make sure
        // code generator implements this method
        return input;
    }

    /**
     * Escape single and/or double quote to avoid code injection
     *
     * @param input String to be cleaned up
     * @return string with quotation mark removed or escaped
     */
    public String escapeQuotationMark(String input) {
        LOGGER.warn("escapeQuotationMark should be overridden in the code generator with proper logic to escape " +
                "single/double quote");
        return input.replace("\"", "\\\"");
    }

    public Set defaultIncludes() {
        return defaultIncludes;
    }

    public Map typeMapping() {
        return typeMapping;
    }

    public Map instantiationTypes() {
        return instantiationTypes;
    }

    public Set reservedWords() {
        return reservedWords;
    }

    public Set languageSpecificPrimitives() {
        return languageSpecificPrimitives;
    }

    public Map importMapping() {
        return importMapping;
    }

    public String testPackage() {
        return testPackage;
    }

    public String modelPackage() {
        return modelPackage;
    }

    public String apiPackage() {
        return apiPackage;
    }

    public String fileSuffix() {
        return fileSuffix;
    }

    public String templateDir() {
        return templateDir;
    }

    public String embeddedTemplateDir() {
        if (embeddedTemplateDir != null) {
            return embeddedTemplateDir;
        } else {
            return templateDir;
        }
    }

    public String getCommonTemplateDir() {
        return this.commonTemplateDir;
    }

    public void setCommonTemplateDir(String commonTemplateDir) {
        this.commonTemplateDir = commonTemplateDir;
    }

    public Map apiDocTemplateFiles() {
        return apiDocTemplateFiles;
    }

    public Map modelDocTemplateFiles() {
        return modelDocTemplateFiles;
    }

    public Map reservedWordsMappings() {
        return reservedWordsMappings;
    }

    public Map apiTestTemplateFiles() {
        return apiTestTemplateFiles;
    }

    public Map modelTestTemplateFiles() {
        return modelTestTemplateFiles;
    }

    public Map apiTemplateFiles() {
        return apiTemplateFiles;
    }

    public Map modelTemplateFiles() {
        return modelTemplateFiles;
    }

    public String apiFileFolder() {
        return outputFolder + File.separator + apiPackage().replace('.', File.separatorChar);
    }

    public String modelFileFolder() {
        return outputFolder + File.separator + modelPackage().replace('.', File.separatorChar);
    }

    public String apiTestFileFolder() {
        return outputFolder + File.separator + testPackage().replace('.', File.separatorChar);
    }

    public String modelTestFileFolder() {
        return outputFolder + File.separator + testPackage().replace('.', File.separatorChar);
    }

    public String apiDocFileFolder() {
        return outputFolder;
    }

    public String modelDocFileFolder() {
        return outputFolder;
    }

    public Map additionalProperties() {
        return additionalProperties;
    }

    public Map serverVariableOverrides() {
        return serverVariables;
    }

    public Map vendorExtensions() {
        return vendorExtensions;
    }

    public List supportingFiles() {
        return supportingFiles;
    }

    public String outputFolder() {
        return outputFolder;
    }

    public void setOutputDir(String dir) {
        this.outputFolder = dir;
    }

    public String getOutputDir() {
        return outputFolder();
    }

    public String getInputSpec() {
        return inputSpec;
    }

    public void setInputSpec(String inputSpec) {
        this.inputSpec = inputSpec;
    }

    public void setTemplateDir(String templateDir) {
        this.templateDir = templateDir;
    }

    public void setModelPackage(String modelPackage) {
        this.modelPackage = modelPackage;
    }

    public String getModelNamePrefix() {
        return modelNamePrefix;
    }

    public void setModelNamePrefix(String modelNamePrefix) {
        this.modelNamePrefix = modelNamePrefix;
    }

    public String getModelNameSuffix() {
        return modelNameSuffix;
    }

    public void setModelNameSuffix(String modelNameSuffix) {
        this.modelNameSuffix = modelNameSuffix;
    }

    public String getApiNameSuffix() {
        return apiNameSuffix;
    }

    public void setApiNameSuffix(String apiNameSuffix) {
        this.apiNameSuffix = apiNameSuffix;
    }

    public void setApiPackage(String apiPackage) {
        this.apiPackage = apiPackage;
    }

    public Boolean getSortParamsByRequiredFlag() {
        return sortParamsByRequiredFlag;
    }

    public void setSortParamsByRequiredFlag(Boolean sortParamsByRequiredFlag) {
        this.sortParamsByRequiredFlag = sortParamsByRequiredFlag;
    }

    public Boolean getSortModelPropertiesByRequiredFlag() {
        return sortModelPropertiesByRequiredFlag;
    }

    public void setSortModelPropertiesByRequiredFlag(Boolean sortModelPropertiesByRequiredFlag) {
        this.sortModelPropertiesByRequiredFlag = sortModelPropertiesByRequiredFlag;
    }

    public Boolean getPrependFormOrBodyParameters() {
        return prependFormOrBodyParameters;
    }

    public void setPrependFormOrBodyParameters(Boolean prependFormOrBodyParameters) {
        this.prependFormOrBodyParameters = prependFormOrBodyParameters;
    }

    public Boolean getEnsureUniqueParams() {
        return ensureUniqueParams;
    }

    public void setEnsureUniqueParams(Boolean ensureUniqueParams) {
        this.ensureUniqueParams = ensureUniqueParams;
    }

    public Boolean getAllowUnicodeIdentifiers() {
        return allowUnicodeIdentifiers;
    }

    public void setAllowUnicodeIdentifiers(Boolean allowUnicodeIdentifiers) {
        this.allowUnicodeIdentifiers = allowUnicodeIdentifiers;
    }

    public Boolean getUseOneOfInterfaces() { return useOneOfInterfaces; }

    public void setUseOneOfInterfaces(Boolean useOneOfInterfaces) {
        this.useOneOfInterfaces = useOneOfInterfaces;
    }

    /**
     * Return the regular expression/JSON schema pattern (http://json-schema.org/latest/json-schema-validation.html#anchor33)
     *
     * @param pattern the pattern (regular expression)
     * @return properly-escaped pattern
     */
    public String toRegularExpression(String pattern) {
        return addRegularExpressionDelimiter(escapeText(pattern));
    }

    /**
     * Return the file name of the Api Test
     *
     * @param name the file name of the Api
     * @return the file name of the Api
     */
    public String toApiFilename(String name) {
        return toApiName(name);
    }

    /**
     * Return the file name of the Api Documentation
     *
     * @param name the file name of the Api
     * @return the file name of the Api
     */
    public String toApiDocFilename(String name) {
        return toApiName(name);
    }

    /**
     * Return the file name of the Api Test
     *
     * @param name the file name of the Api
     * @return the file name of the Api
     */
    public String toApiTestFilename(String name) {
        return toApiName(name) + "Test";
    }

    /**
     * Return the variable name in the Api
     *
     * @param name the varible name of the Api
     * @return the snake-cased variable name
     */
    public String toApiVarName(String name) {
        return lowerCamelCase(name);
    }

    /**
     * Return the capitalized file name of the model
     *
     * @param name the model name
     * @return the file name of the model
     */
    public String toModelFilename(String name) {
        return camelize(name);
    }

    /**
     * Return the capitalized file name of the model test
     *
     * @param name the model name
     * @return the file name of the model
     */
    public String toModelTestFilename(String name) {
        return camelize(name) + "Test";
    }

    /**
     * Return the capitalized file name of the model documentation
     *
     * @param name the model name
     * @return the file name of the model
     */
    public String toModelDocFilename(String name) {
        return camelize(name);
    }

    /**
     * Returns metadata about the generator.
     *
     * @return A provided {@link GeneratorMetadata} instance
     */
    @Override
    public GeneratorMetadata getGeneratorMetadata() {
        return generatorMetadata;
    }

    /**
     * Return the operation ID (method name)
     *
     * @param operationId operation ID
     * @return the sanitized method name
     */
    @SuppressWarnings("static-method")
    public String toOperationId(String operationId) {
        // throw exception if method name is empty
        if (StringUtils.isEmpty(operationId)) {
            throw new RuntimeException("Empty method name (operationId) not allowed");
        }

        return operationId;
    }

    /**
     * Return the variable name by removing invalid characters and proper escaping if
     * it's a reserved word.
     *
     * @param name the variable name
     * @return the sanitized variable name
     */
    public String toVarName(String name) {
        if (reservedWords.contains(name)) {
            return escapeReservedWord(name);
        } else if (((CharSequence) name).chars().anyMatch(character -> specialCharReplacements.keySet().contains("" + ((char) character)))) {
            return escape(name, specialCharReplacements, null, null);
        } else {
            return name;
        }
    }

    /**
     * Return the parameter name by removing invalid characters and proper escaping if
     * it's a reserved word.
     *
     * @param name Codegen property object
     * @return the sanitized parameter name
     */
    public String toParamName(String name) {
        name = removeNonNameElementToCamelCase(name); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'.
        if (reservedWords.contains(name)) {
            return escapeReservedWord(name);
        } else if (((CharSequence) name).chars().anyMatch(character -> specialCharReplacements.keySet().contains("" + ((char) character)))) {
            return escape(name, specialCharReplacements, null, null);
        }
        return name;
    }

    /**
     * Return the parameter name of array of model
     *
     * @param name name of the array model
     * @return the sanitized parameter name
     */
    public String toArrayModelParamName(String name) {
        return toParamName(name);
    }

    /**
     * Return the Enum name (e.g. StatusEnum given 'status')
     *
     * @param property Codegen property
     * @return the Enum name
     */
    @SuppressWarnings("static-method")
    public String toEnumName(CodegenProperty property) {
        return StringUtils.capitalize(property.name) + "Enum";
    }

    /**
     * Return the escaped name of the reserved word
     *
     * @param name the name to be escaped
     * @return the escaped reserved word
     * 

* throws Runtime exception as reserved word is not allowed (default behavior) */ @SuppressWarnings("static-method") public String escapeReservedWord(String name) { throw new RuntimeException("reserved word " + name + " not allowed"); } /** * Return the fully-qualified "Model" name for import * * @param name the name of the "Model" * @return the fully-qualified "Model" name for import */ public String toModelImport(String name) { if ("".equals(modelPackage())) { return name; } else { return modelPackage() + "." + name; } } /** * Return the fully-qualified "Api" name for import * * @param name the name of the "Api" * @return the fully-qualified "Api" name for import */ public String toApiImport(String name) { return apiPackage() + "." + name; } /** * Default constructor. * This method will map between OAS type and language-specified type, as well as mapping * between OAS type and the corresponding import statement for the language. This will * also add some language specified CLI options, if any. * returns string presentation of the example path (it's a constructor) */ public DefaultCodegen() { CodegenType codegenType = getTag(); if (codegenType == null) { codegenType = CodegenType.OTHER; } generatorMetadata = GeneratorMetadata.newBuilder() .stability(Stability.STABLE) .featureSet(DefaultFeatureSet) .generationMessage(String.format(Locale.ROOT, "OpenAPI Generator: %s (%s)", getName(), codegenType.toValue())) .build(); defaultIncludes = new HashSet( Arrays.asList("double", "int", "long", "short", "char", "float", "String", "boolean", "Boolean", "Double", "Void", "Integer", "Long", "Float") ); typeMapping = new HashMap(); typeMapping.put("array", "List"); typeMapping.put("map", "Map"); typeMapping.put("List", "List"); typeMapping.put("boolean", "Boolean"); typeMapping.put("string", "String"); typeMapping.put("int", "Integer"); typeMapping.put("float", "Float"); typeMapping.put("number", "BigDecimal"); typeMapping.put("DateTime", "Date"); typeMapping.put("long", "Long"); typeMapping.put("short", "Short"); typeMapping.put("char", "String"); typeMapping.put("double", "Double"); typeMapping.put("object", "Object"); typeMapping.put("integer", "Integer"); typeMapping.put("ByteArray", "byte[]"); typeMapping.put("binary", "File"); typeMapping.put("file", "File"); typeMapping.put("UUID", "UUID"); typeMapping.put("URI", "URI"); typeMapping.put("BigDecimal", "BigDecimal"); instantiationTypes = new HashMap(); reservedWords = new HashSet(); // TODO: Move Java specific import mappings out of DefaultCodegen. importMapping = new HashMap(); importMapping.put("BigDecimal", "java.math.BigDecimal"); importMapping.put("UUID", "java.util.UUID"); importMapping.put("URI", "java.net.URI"); importMapping.put("File", "java.io.File"); importMapping.put("Date", "java.util.Date"); importMapping.put("Timestamp", "java.sql.Timestamp"); importMapping.put("Map", "java.util.Map"); importMapping.put("HashMap", "java.util.HashMap"); importMapping.put("Array", "java.util.List"); importMapping.put("ArrayList", "java.util.ArrayList"); importMapping.put("List", "java.util.*"); importMapping.put("Set", "java.util.*"); importMapping.put("DateTime", "org.joda.time.*"); importMapping.put("LocalDateTime", "org.joda.time.*"); importMapping.put("LocalDate", "org.joda.time.*"); importMapping.put("LocalTime", "org.joda.time.*"); // we've used the .openapi-generator-ignore approach as // suppportingFiles can be cleared by code generator that extends // the default codegen, leaving the commented code below for // future reference //supportingFiles.add(new GlobalSupportingFile("LICENSE", "LICENSE")); 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())); // initialize special character mapping initalizeSpecialCharacterMapping(); // Register common Mustache lambdas. registerMustacheLambdas(); } /** * Initialize special character mapping */ protected void initalizeSpecialCharacterMapping() { // Initialize special characters specialCharReplacements.put("$", "Dollar"); specialCharReplacements.put("^", "Caret"); specialCharReplacements.put("|", "Pipe"); specialCharReplacements.put("=", "Equal"); specialCharReplacements.put("*", "Star"); specialCharReplacements.put("-", "Minus"); specialCharReplacements.put("&", "Ampersand"); specialCharReplacements.put("%", "Percent"); specialCharReplacements.put("#", "Hash"); specialCharReplacements.put("@", "At"); specialCharReplacements.put("!", "Exclamation"); specialCharReplacements.put("+", "Plus"); specialCharReplacements.put(":", "Colon"); specialCharReplacements.put(">", "Greater_Than"); specialCharReplacements.put("<", "Less_Than"); specialCharReplacements.put(".", "Period"); specialCharReplacements.put("_", "Underscore"); specialCharReplacements.put("?", "Question_Mark"); specialCharReplacements.put(",", "Comma"); specialCharReplacements.put("'", "Quote"); specialCharReplacements.put("\"", "Double_Quote"); specialCharReplacements.put("/", "Slash"); specialCharReplacements.put("\\", "Back_Slash"); specialCharReplacements.put("(", "Left_Parenthesis"); specialCharReplacements.put(")", "Right_Parenthesis"); specialCharReplacements.put("{", "Left_Curly_Bracket"); specialCharReplacements.put("}", "Right_Curly_Bracket"); specialCharReplacements.put("[", "Left_Square_Bracket"); specialCharReplacements.put("]", "Right_Square_Bracket"); specialCharReplacements.put("~", "Tilde"); specialCharReplacements.put("`", "Backtick"); specialCharReplacements.put("<=", "Less_Than_Or_Equal_To"); specialCharReplacements.put(">=", "Greater_Than_Or_Equal_To"); specialCharReplacements.put("!=", "Not_Equal"); } /** * Return the symbol name of a symbol * * @param input Symbol (e.g. $) * @return Symbol name (e.g. Dollar) */ protected String getSymbolName(String input) { return specialCharReplacements.get(input); } /** * Return the example path * * @param path the path of the operation * @param operation OAS operation object * @return string presentation of the example path */ @SuppressWarnings("static-method") public String generateExamplePath(String path, Operation operation) { StringBuilder sb = new StringBuilder(); sb.append(path); if (operation.getParameters() != null) { int count = 0; for (Parameter param : operation.getParameters()) { if (param instanceof QueryParameter) { StringBuilder paramPart = new StringBuilder(); QueryParameter qp = (QueryParameter) param; if (count == 0) { paramPart.append("?"); } else { paramPart.append(","); } count += 1; if (!param.getRequired()) { paramPart.append("["); } paramPart.append(param.getName()).append("="); paramPart.append("{"); // TODO support for multi, tsv? if (qp.getStyle() != null) { paramPart.append(param.getName()).append("1"); if (Parameter.StyleEnum.FORM.equals(qp.getStyle())) { if (qp.getExplode() != null && qp.getExplode()) { paramPart.append(","); } else { paramPart.append("&").append(param.getName()).append("="); paramPart.append(param.getName()).append("2"); } } else if (Parameter.StyleEnum.PIPEDELIMITED.equals(qp.getStyle())) { paramPart.append("|"); } else if (Parameter.StyleEnum.SPACEDELIMITED.equals(qp.getStyle())) { paramPart.append("%20"); } else { LOGGER.warn("query parameter '" + param.getName() + "style not support: " + qp.getStyle()); } } else { paramPart.append(param.getName()); } paramPart.append("}"); if (!param.getRequired()) { paramPart.append("]"); } sb.append(paramPart.toString()); } } } return sb.toString(); } /** * Return the instantiation type of the property, especially for map and array * * @param schema property schema * @return string presentation of the instantiation type of the property */ public String toInstantiationType(Schema schema) { if (ModelUtils.isMapSchema(schema)) { Schema additionalProperties = ModelUtils.getAdditionalProperties(schema); String inner = getSchemaType(additionalProperties); return instantiationTypes.get("map") + ""; } else if (ModelUtils.isArraySchema(schema)) { ArraySchema arraySchema = (ArraySchema) schema; String inner = getSchemaType(getSchemaItems(arraySchema)); return instantiationTypes.get("array") + "<" + inner + ">"; } else { return null; } } /** * Return the example value of the parameter. * * @param codegenParameter Codegen parameter */ public void setParameterExampleValue(CodegenParameter codegenParameter) { // set the example value // if not specified in x-example, generate a default value // TODO need to revise how to obtain the example value if (codegenParameter.vendorExtensions != null && codegenParameter.vendorExtensions.containsKey("x-example")) { codegenParameter.example = Json.pretty(codegenParameter.vendorExtensions.get("x-example")); } else if (Boolean.TRUE.equals(codegenParameter.isBoolean)) { codegenParameter.example = "true"; } else if (Boolean.TRUE.equals(codegenParameter.isLong)) { codegenParameter.example = "789"; } else if (Boolean.TRUE.equals(codegenParameter.isInteger)) { codegenParameter.example = "56"; } else if (Boolean.TRUE.equals(codegenParameter.isFloat)) { codegenParameter.example = "3.4"; } else if (Boolean.TRUE.equals(codegenParameter.isDouble)) { codegenParameter.example = "1.2"; } else if (Boolean.TRUE.equals(codegenParameter.isNumber)) { codegenParameter.example = "8.14"; } else if (Boolean.TRUE.equals(codegenParameter.isBinary)) { codegenParameter.example = "BINARY_DATA_HERE"; } else if (Boolean.TRUE.equals(codegenParameter.isByteArray)) { codegenParameter.example = "BYTE_ARRAY_DATA_HERE"; } else if (Boolean.TRUE.equals(codegenParameter.isFile)) { codegenParameter.example = "/path/to/file.txt"; } else if (Boolean.TRUE.equals(codegenParameter.isDate)) { codegenParameter.example = "2013-10-20"; } else if (Boolean.TRUE.equals(codegenParameter.isDateTime)) { codegenParameter.example = "2013-10-20T19:20:30+01:00"; } else if (Boolean.TRUE.equals(codegenParameter.isUuid)) { codegenParameter.example = "38400000-8cf0-11bd-b23e-10b96e4ef00d"; } else if (Boolean.TRUE.equals(codegenParameter.isUri)) { codegenParameter.example = "https://openapi-generator.tech"; } else if (Boolean.TRUE.equals(codegenParameter.isString)) { codegenParameter.example = codegenParameter.paramName + "_example"; } else if (Boolean.TRUE.equals(codegenParameter.isFreeFormObject)) { codegenParameter.example = "Object"; } } /** * Return the example value of the parameter. * * @param codegenParameter Codegen parameter * @param parameter Parameter */ public void setParameterExampleValue(CodegenParameter codegenParameter, Parameter parameter) { if (parameter.getExample() != null) { codegenParameter.example = parameter.getExample().toString(); return; } if (parameter.getExamples() != null && !parameter.getExamples().isEmpty()) { Example example = parameter.getExamples().values().iterator().next(); if (example.getValue() != null) { codegenParameter.example = example.getValue().toString(); return; } } Schema schema = parameter.getSchema(); if (schema != null && schema.getExample() != null) { codegenParameter.example = schema.getExample().toString(); return; } setParameterExampleValue(codegenParameter); } /** * Return the example value of the parameter. * * @param codegenParameter Codegen parameter * @param requestBody Request body */ public void setParameterExampleValue(CodegenParameter codegenParameter, RequestBody requestBody) { Content content = requestBody.getContent(); if (content.size() > 1) { // @see ModelUtils.getSchemaFromContent() LOGGER.warn("Multiple MediaTypes found, using only the first one"); } MediaType mediaType = content.values().iterator().next(); if (mediaType.getExample() != null) { codegenParameter.example = mediaType.getExample().toString(); return; } if (mediaType.getExamples() != null && !mediaType.getExamples().isEmpty()) { Example example = mediaType.getExamples().values().iterator().next(); if (example.getValue() != null) { codegenParameter.example = example.getValue().toString(); return; } } setParameterExampleValue(codegenParameter); } /** * Return the example value of the property * * @param schema Property schema * @return string presentation of the example value of the property */ public String toExampleValue(Schema schema) { if (schema.getExample() != null) { return schema.getExample().toString(); } return getPropertyDefaultValue(schema); } /** * Return the default value of the property * * @param schema Property schema * @return string presentation of the default value of the property */ @SuppressWarnings("static-method") public String toDefaultValue(Schema schema) { if (schema.getDefault() != null) { return schema.getDefault().toString(); } return getPropertyDefaultValue(schema); } /** * Return property value depending on property type. * * @param schema property type * @return property value */ @SuppressWarnings("squid:S3923") private String getPropertyDefaultValue(Schema schema) { /** * Although all branches return null, this is left intentionally as examples for new contributors */ if (ModelUtils.isBooleanSchema(schema)) { return "null"; } else if (ModelUtils.isDateSchema(schema)) { return "null"; } else if (ModelUtils.isDateTimeSchema(schema)) { return "null"; } else if (ModelUtils.isNumberSchema(schema)) { return "null"; } else if (ModelUtils.isIntegerSchema(schema)) { return "null"; } else if (ModelUtils.isStringSchema(schema)) { return "null"; } else if (ModelUtils.isObjectSchema(schema)) { return "null"; } else { return "null"; } } /** * Return the property initialized from a data object * Useful for initialization with a plain object in Javascript * * @param name Name of the property object * @param schema Property schema * @return string presentation of the default value of the property */ @SuppressWarnings("static-method") public String toDefaultValueWithParam(String name, Schema schema) { return " = data." + name + ";"; } /** * returns the OpenAPI type for the property. Use getAlias to handle $ref of primitive type * * @param schema property schema * @return string presentation of the type **/ @SuppressWarnings("static-method") public String getSchemaType(Schema schema) { if (schema instanceof ComposedSchema) { // composed schema ComposedSchema cs = (ComposedSchema) schema; // Get the interfaces, i.e. the set of elements under 'allOf', 'anyOf' or 'oneOf'. List schemas = ModelUtils.getInterfaces(cs); List names = new ArrayList<>(); // Build a list of the schema types under each interface. // For example, if a 'allOf' composed schema has $ref children, // add the type of each child to the list of names. for (Schema s : schemas) { names.add(getSingleSchemaType(s)); } if (cs.getAllOf() != null) { return toAllOfName(names, cs); } else if (cs.getAnyOf() != null) { // anyOf return toAnyOfName(names, cs); } else if (cs.getOneOf() != null) { // oneOf return toOneOfName(names, cs); } } return getSingleSchemaType(schema); } protected Schema getSchemaItems(ArraySchema schema) { Schema items = schema.getItems(); if (items == null) { LOGGER.error("Undefined array inner type for `{}`. Default to String.", schema.getName()); items = new StringSchema().description("TODO default missing array inner type to string"); schema.setItems(items); } return items; } protected Schema getSchemaAdditionalProperties(Schema schema) { Schema inner = 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, ComposedSchema composedSchema) { if (names.size() == 0) { LOGGER.error("allOf has no member defined: {}. Default to ERROR_ALLOF_SCHEMA", composedSchema); return "ERROR_ALLOF_SCHEMA"; } else if (names.size() == 1) { return names.get(0); } else { LOGGER.warn("allOf with multiple schemas defined. Using only the first one: {}", names.get(0)); return names.get(0); } } /** * Return the name of the anyOf schema * * @param names List of names * @param composedSchema composed schema * @return name of the anyOf schema */ @SuppressWarnings("static-method") public String toAnyOfName(List names, ComposedSchema composedSchema) { return "anyOf<" + String.join(",", names) + ">"; } /** * Return the name of the oneOf schema * * @param names List of names * @param composedSchema composed schema * @return name of the oneOf schema */ @SuppressWarnings("static-method") public String toOneOfName(List names, ComposedSchema composedSchema) { Map exts = composedSchema.getExtensions(); if (exts != null && exts.containsKey("x-oneOf-name")) { return (String) exts.get("x-oneOf-name"); } return "oneOf<" + String.join(",", names) + ">"; } /** * Return a string representation of the schema type, resolving aliasing and references if necessary. * * @param schema * @return the string representation of the schema type. */ private String getSingleSchemaType(Schema schema) { Schema unaliasSchema = ModelUtils.unaliasSchema(this.openAPI, schema, importMapping); if (StringUtils.isNotBlank(unaliasSchema.get$ref())) { // reference to another definition/schema // get the schema/model name from $ref String schemaName = ModelUtils.getSimpleRef(unaliasSchema.get$ref()); if (StringUtils.isNotEmpty(schemaName)) { if (importMapping.containsKey(schemaName)) { return schemaName; } return getAlias(schemaName); } else { LOGGER.warn("Error obtaining the datatype from ref:" + unaliasSchema.get$ref() + ". Default to 'object'"); return "object"; } } else { // primitive type or model return getAlias(getPrimitiveType(unaliasSchema)); } } /** * Return the OAI type (e.g. integer, long, etc) corresponding to a schema. *

$ref
is not taken into account by this method. * * If the schema is free-form (i.e. 'type: object' with no properties) or inline * schema, the returned OAI type is 'object'. * * @param schema * @return type */ private String getPrimitiveType(Schema schema) { if (schema == null) { throw new RuntimeException("schema cannot be null in getPrimitiveType"); } else if (ModelUtils.isNullType(schema)) { // The 'null' type is allowed in OAS 3.1 and above. It is not supported by OAS 3.0.x, // though this tooling supports it. return "null"; } else if (ModelUtils.isStringSchema(schema) && "number".equals(schema.getFormat())) { // special handle of type: string, format: number return "BigDecimal"; } else if (ModelUtils.isByteArraySchema(schema)) { return "ByteArray"; } else if (ModelUtils.isFileSchema(schema)) { return "file"; } else if (ModelUtils.isBinarySchema(schema)) { return SchemaTypeUtil.BINARY_FORMAT; } else if (ModelUtils.isBooleanSchema(schema)) { return SchemaTypeUtil.BOOLEAN_TYPE; } else if (ModelUtils.isDateSchema(schema)) { return SchemaTypeUtil.DATE_FORMAT; } else if (ModelUtils.isDateTimeSchema(schema)) { return "DateTime"; } else if (ModelUtils.isNumberSchema(schema)) { if (schema.getFormat() == null) { // no format defined return "number"; } else if (ModelUtils.isFloatSchema(schema)) { return SchemaTypeUtil.FLOAT_FORMAT; } else if (ModelUtils.isDoubleSchema(schema)) { return SchemaTypeUtil.DOUBLE_FORMAT; } else { LOGGER.warn("Unknown `format` {} detected for type `number`. Defaulting to `number`", schema.getFormat()); return "number"; } } else if (ModelUtils.isIntegerSchema(schema)) { if (ModelUtils.isLongSchema(schema)) { return "long"; } else { return schema.getType(); // integer } } else if (ModelUtils.isMapSchema(schema)) { return "map"; } else if (ModelUtils.isArraySchema(schema)) { 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)) { return "object"; } else if (schema.getProperties() != null && !schema.getProperties().isEmpty()) { // having property implies it's a model return "object"; } else if (StringUtils.isNotEmpty(schema.getType())) { LOGGER.warn("Unknown type found in the schema: " + schema.getType()); return schema.getType(); } 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 type declaration of a given name * * @param name name * @return a string presentation of the type */ @SuppressWarnings("static-method") public String getTypeDeclaration(String name) { return name; } /** * Output the type declaration of the property * * @param schema property schema * @return a string presentation of the property type */ public String getTypeDeclaration(Schema schema) { if (schema == null) { LOGGER.warn("Null schema found. Default type to `NULL_SCHMEA_ERR`"); return "NULL_SCHMEA_ERR"; } String oasType = getSchemaType(schema); if (typeMapping.containsKey(oasType)) { return typeMapping.get(oasType); } return oasType; } /** * Determine the type alias for the given type if it exists. This feature * was originally developed for Java because the language does not have an aliasing * mechanism of its own but later extends to handle other languages * * @param name The type name. * @return The alias of the given type, if it exists. If there is no alias * for this type, then returns the input type name. */ public String getAlias(String name) { if (typeAliases != null && typeAliases.containsKey(name)) { return typeAliases.get(name); } return name; } /** * Output the Getter name for boolean property, e.g. getActive * * @param name the name of the property * @return getter name based on naming convention */ public String toBooleanGetter(String name) { return "get" + getterAndSetterCapitalize(name); } /** * Output the Getter name, e.g. getSize * * @param name the name of the property * @return getter name based on naming convention */ public String toGetter(String name) { return "get" + getterAndSetterCapitalize(name); } /** * Output the Setter name, e.g. setSize * * @param name the name of the property * @return setter name based on naming convention */ public String toSetter(String name) { return "set" + getterAndSetterCapitalize(name); } /** * Output the API (class) name (capitalized) ending with the specified or default suffix * Return DefaultApi if name is empty * * @param name the name of the Api * @return capitalized Api name */ public String toApiName(String name) { if (name.length() == 0) { return "DefaultApi"; } return camelize(name + "_" + apiNameSuffix); } /** * Output the proper model name (capitalized). * In case the name belongs to the TypeSystem it won't be renamed. * * @param name the name of the model * @return capitalized model name */ public String toModelName(final String name) { return camelize(modelNamePrefix + "_" + name + "_" + modelNameSuffix); } /** * Convert OAS Model object to Codegen Model object * * @param name the name of the model * @param schema OAS Model object * @return Codegen Model object */ public CodegenModel fromModel(String name, Schema schema) { Map allDefinitions = ModelUtils.getSchemas(this.openAPI); if (typeAliases == null) { // Only do this once during first call typeAliases = getAllAliases(allDefinitions); } // unalias schema schema = ModelUtils.unaliasSchema(this.openAPI, schema, importMapping); if (schema == null) { LOGGER.warn("Schema {} not found", name); return null; } CodegenModel m = CodegenModelFactory.newInstance(CodegenModelType.MODEL); if (reservedWords.contains(name)) { m.name = escapeReservedWord(name); } else { m.name = name; } m.title = escapeText(schema.getTitle()); m.description = escapeText(schema.getDescription()); m.unescapedDescription = schema.getDescription(); m.classname = toModelName(name); m.classVarName = toVarName(name); m.classFilename = toModelFilename(name); m.modelJson = Json.pretty(schema); m.externalDocumentation = schema.getExternalDocs(); if (schema.getExtensions() != null && !schema.getExtensions().isEmpty()) { m.getVendorExtensions().putAll(schema.getExtensions()); } m.isAlias = (typeAliases.containsKey(name) || isAliasOfSimpleTypes(schema)); // check if the unaliased schema is an alias of simple OAS types m.discriminator = createDiscriminator(name, schema); if (schema.getXml() != null) { m.xmlPrefix = schema.getXml().getPrefix(); m.xmlNamespace = schema.getXml().getNamespace(); m.xmlName = schema.getXml().getName(); } if (ModelUtils.isArraySchema(schema)) { m.isArrayModel = true; m.arrayModelType = fromProperty(name, schema).complexType; addParentContainer(m, name, schema); } else if (schema instanceof ComposedSchema) { final ComposedSchema composed = (ComposedSchema) schema; Map properties = new LinkedHashMap(); List required = new ArrayList(); Map allProperties = new LinkedHashMap(); List allRequired = new ArrayList(); // if schema has properties outside of allOf/oneOf/anyOf also add them to m if (composed.getProperties() != null && !composed.getProperties().isEmpty()) { if (composed.getOneOf() != null && !composed.getOneOf().isEmpty()) { LOGGER.warn("'oneOf' is intended to include only the additional optional OAS extension discriminator object. " + "For more details, see https://json-schema.org/draft/2019-09/json-schema-core.html#rfc.section.9.2.1.3 and the OAS section on 'Composition and Inheritance'."); } addVars(m, unaliasPropertySchema(composed.getProperties()), composed.getRequired(), null, null); } // parent model final String parentName = ModelUtils.getParentName(composed, allDefinitions); final List allParents = ModelUtils.getAllParentsName(composed, allDefinitions, false); final Schema parent = StringUtils.isBlank(parentName) || allDefinitions == null ? null : allDefinitions.get(parentName); // TODO revise the logic below to set dicriminator, xml attributes if (supportsInheritance || supportsMixins) { m.allVars = new ArrayList(); if (composed.getAllOf() != null) { int modelImplCnt = 0; // only one inline object allowed in a ComposedModel int modelDiscriminators = 0; // only one discriminator allowed in a ComposedModel for (Schema innerSchema : composed.getAllOf()) { // TODO need to work with anyOf, oneOf as well if (m.discriminator == null && innerSchema.getDiscriminator() != null) { LOGGER.debug("discriminator is set to null (not correctly set earlier): {}", name); m.discriminator = createDiscriminator(name, innerSchema); modelDiscriminators++; } if (innerSchema.getXml() != null) { m.xmlPrefix = innerSchema.getXml().getPrefix(); m.xmlNamespace = innerSchema.getXml().getNamespace(); m.xmlName = innerSchema.getXml().getName(); } if (modelDiscriminators > 1) { LOGGER.error("Allof composed schema is inheriting >1 discriminator. Only use one discriminator: {}", composed); } if (modelImplCnt++ > 1) { LOGGER.warn("More than one inline schema specified in allOf:. Only the first one is recognized. All others are ignored."); break; // only one schema with discriminator allowed in allOf } } } } // interfaces (schemas defined in allOf, anyOf, oneOf) List interfaces = ModelUtils.getInterfaces(composed); if (!interfaces.isEmpty()) { // m.interfaces is for backward compatibility if (m.interfaces == null) m.interfaces = new ArrayList(); for (Schema interfaceSchema : interfaces) { if (StringUtils.isBlank(interfaceSchema.get$ref())) { continue; } Schema refSchema = null; String ref = ModelUtils.getSimpleRef(interfaceSchema.get$ref()); if (allDefinitions != null) { refSchema = allDefinitions.get(ref); } final String modelName = toModelName(ref); m.interfaces.add(modelName); addImport(m, modelName); if (allDefinitions != null && refSchema != null) { if (allParents.contains(ref) && supportsMultipleInheritance) { // multiple inheritance addProperties(allProperties, allRequired, refSchema); } else if (parentName != null && parentName.equals(ref) && supportsInheritance) { // single inheritance addProperties(allProperties, allRequired, refSchema); } else { // composition addProperties(properties, required, refSchema); addProperties(allProperties, allRequired, refSchema); } } if (composed.getAnyOf() != null) { m.anyOf.add(modelName); } else if (composed.getOneOf() != null) { m.oneOf.add(modelName); } else if (composed.getAllOf() != null) { m.allOf.add(modelName); } else { LOGGER.error("Composed schema has incorrect anyOf, allOf, oneOf defined: {}", composed); } } } if (parent != null) { m.parentSchema = parentName; m.parent = toModelName(parentName); if (supportsMultipleInheritance) { m.allParents = new ArrayList(); for (String pname : allParents) { String pModelName = toModelName(pname); m.allParents.add(pModelName); addImport(m, pModelName); } } else { // single inheritance addImport(m, m.parent); } } // child schema (properties owned by the schema itself) for (Schema component : interfaces) { if (component.get$ref() == null) { if (component != null) { // component is the child schema addProperties(properties, required, component); // includes child's properties (all, required) in allProperties, allRequired addProperties(allProperties, allRequired, component); } break; // at most one child only } } if (composed.getRequired() != null) { required.addAll(composed.getRequired()); allRequired.addAll(composed.getRequired()); } addVars(m, unaliasPropertySchema(properties), required, unaliasPropertySchema(allProperties), allRequired); // end of code block for composed schema } else { m.dataType = getSchemaType(schema); if (schema.getEnum() != null && !schema.getEnum().isEmpty()) { m.isEnum = true; // comment out below as allowableValues is not set in post processing model enum m.allowableValues = new HashMap(); m.allowableValues.put("values", schema.getEnum()); } if (ModelUtils.isMapSchema(schema)) { addAdditionPropertiesToCodeGenModel(m, schema); m.isMapModel = true; } else if (ModelUtils.isIntegerSchema(schema)) { // integer type // NOTE: Integral schemas as CodegenModel is a rare use case and may be removed at a later date. // Sync of properties is done for consistency with other data types like CodegenParameter/CodegenProperty. ModelUtils.syncValidationProperties(schema, m); m.isNumeric = Boolean.TRUE; if (ModelUtils.isLongSchema(schema)) { // int64/long format m.isLong = Boolean.TRUE; } else { // int32 format m.isInteger = Boolean.TRUE; } } else if (ModelUtils.isStringSchema(schema)) { // NOTE: String schemas as CodegenModel is a rare use case and may be removed at a later date. // Sync of properties is done for consistency with other data types like CodegenParameter/CodegenProperty. ModelUtils.syncValidationProperties(schema, m); m.isString = Boolean.TRUE; } else if (ModelUtils.isNumberSchema(schema)) { // NOTE: Number schemas as CodegenModel is a rare use case and may be removed at a later date. // Sync of properties is done for consistency with other data types like CodegenParameter/CodegenProperty. ModelUtils.syncValidationProperties(schema, m); m.isNumeric = Boolean.TRUE; if (ModelUtils.isFloatSchema(schema)) { // float m.isFloat = Boolean.TRUE; } else if (ModelUtils.isDoubleSchema(schema)) { // double m.isDouble = Boolean.TRUE; } else { // type is number and without format m.isNumber = Boolean.TRUE; } } if (Boolean.TRUE.equals(schema.getNullable())) { m.isNullable = Boolean.TRUE; } // passing null to allProperties and allRequired as there's no parent addVars(m, unaliasPropertySchema(schema.getProperties()), schema.getRequired(), null, null); } // remove duplicated properties m.removeAllDuplicatedProperty(); // post process model properties if (m.vars != null) { for (CodegenProperty prop : m.vars) { postProcessModelProperty(m, prop); } } if (m.allVars != null) { for (CodegenProperty prop : m.allVars) { postProcessModelProperty(m, prop); } } if (sortModelPropertiesByRequiredFlag) { Collections.sort(m.vars, 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; } }); } return m; } protected CodegenDiscriminator createDiscriminator(String schemaName, Schema schema) { if (schema.getDiscriminator() == null) { return null; } CodegenDiscriminator discriminator = new CodegenDiscriminator(); discriminator.setPropertyName(toVarName(schema.getDiscriminator().getPropertyName())); discriminator.setPropertyBaseName(schema.getDiscriminator().getPropertyName()); discriminator.setPropertyGetter(toGetter(discriminator.getPropertyName())); // FIXME: for now, we assume that the discriminator property is String discriminator.setPropertyType(typeMapping.get("string")); discriminator.setMapping(schema.getDiscriminator().getMapping()); if (schema.getDiscriminator().getMapping() != null && !schema.getDiscriminator().getMapping().isEmpty()) { for (Entry e : schema.getDiscriminator().getMapping().entrySet()) { String nameOrRef = e.getValue(); String name = nameOrRef.indexOf('/') >= 0 ? ModelUtils.getSimpleRef(nameOrRef) : nameOrRef; String modelName = toModelName(name); discriminator.getMappedModels().add(new MappedModel(e.getKey(), modelName)); } } else { Map allDefinitions = ModelUtils.getSchemas(this.openAPI); allDefinitions.forEach((childName, child) -> { if (child instanceof ComposedSchema && ((ComposedSchema) child).getAllOf() != null) { final List parentSchemas = ModelUtils.getAllParentsName((ComposedSchema) child, allDefinitions, true); if (parentSchemas.contains(schemaName)) { discriminator.getMappedModels().add(new MappedModel(childName, toModelName(childName))); } } }); } return discriminator; } protected void addAdditionPropertiesToCodeGenModel(CodegenModel codegenModel, Schema schema) { addParentContainer(codegenModel, codegenModel.name, schema); } /** * Add schema's properties to "properties" and "required" list * * @param properties all properties * @param required required property only * @param schema schema in which the properties will be added to the lists */ protected void addProperties(Map properties, List required, Schema schema) { if (schema instanceof ComposedSchema) { ComposedSchema composedSchema = (ComposedSchema) schema; for (Schema component : composedSchema.getAllOf()) { addProperties(properties, required, component); } if (schema.getRequired() != null) { required.addAll(schema.getRequired()); } if (composedSchema.getOneOf() != null) { throw new RuntimeException("Please report the issue: Cannot process oneOf (Composed Scheme) in addProperties: " + schema); } if (composedSchema.getAnyOf() != null) { throw new RuntimeException("Please report the issue: Cannot process anyOf (Composed Schema) in addProperties: " + schema); } return; } if (StringUtils.isNotBlank(schema.get$ref())) { Schema interfaceSchema = ModelUtils.getReferencedSchema(this.openAPI, schema); addProperties(properties, required, interfaceSchema); return; } if (schema.getProperties() != null) { properties.putAll(schema.getProperties()); } if (schema.getRequired() != null) { required.addAll(schema.getRequired()); } } /** * Camelize the method name of the getter and setter * * @param name string to be camelized * @return Camelized string */ public String getterAndSetterCapitalize(String name) { if (name == null || name.length() == 0) { return name; } return camelize(toVarName(name)); } /** * Convert OAS Property object to Codegen Property object * * @param name name of the property * @param p OAS property object * @return Codegen Property object */ public CodegenProperty fromProperty(String name, Schema p) { if (p == null) { LOGGER.error("Undefined property/schema for `{}`. Default to type:string.", name); return null; } LOGGER.debug("debugging fromProperty for " + name + " : " + p); // unalias schema p = ModelUtils.unaliasSchema(this.openAPI, p, importMapping); CodegenProperty property = CodegenModelFactory.newInstance(CodegenModelType.PROPERTY); ModelUtils.syncValidationProperties(p, property); property.name = toVarName(name); property.baseName = name; if (p.getType() == null) { property.openApiType = getSchemaType(p); } else { property.openApiType = p.getType(); } property.nameInCamelCase = camelize(property.name, false); property.nameInSnakeCase = CaseFormat.UPPER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, property.nameInCamelCase); property.description = escapeText(p.getDescription()); property.unescapedDescription = p.getDescription(); property.title = p.getTitle(); property.getter = toGetter(name); property.setter = toSetter(name); property.example = toExampleValue(p); property.defaultValue = toDefaultValue(p); property.defaultValueWithParam = toDefaultValueWithParam(name, p); property.jsonSchema = Json.pretty(p); if (p.getDeprecated() != null) { property.deprecated = p.getDeprecated(); } if (p.getReadOnly() != null) { property.isReadOnly = p.getReadOnly(); } if (p.getWriteOnly() != null) { property.isWriteOnly = p.getWriteOnly(); } if (p.getNullable() != null) { property.isNullable = p.getNullable(); } if (p.getXml() != null) { if (p.getXml().getAttribute() != null) { property.isXmlAttribute = p.getXml().getAttribute(); } property.xmlPrefix = p.getXml().getPrefix(); property.xmlName = p.getXml().getName(); property.xmlNamespace = p.getXml().getNamespace(); } if (p.getExtensions() != null && !p.getExtensions().isEmpty()) { property.getVendorExtensions().putAll(p.getExtensions()); } String type = getSchemaType(p); if (ModelUtils.isIntegerSchema(p)) { // integer type property.isNumeric = Boolean.TRUE; if (ModelUtils.isLongSchema(p)) { // int64/long format property.isLong = Boolean.TRUE; } else { // int32 format property.isInteger = Boolean.TRUE; } if (p.getMinimum() != null) { property.minimum = String.valueOf(p.getMinimum().longValue()); } if (p.getMaximum() != null) { property.maximum = String.valueOf(p.getMaximum().longValue()); } if (p.getExclusiveMinimum() != null) { property.exclusiveMinimum = p.getExclusiveMinimum(); } if (p.getExclusiveMaximum() != null) { property.exclusiveMaximum = p.getExclusiveMaximum(); } // check if any validation rule defined // exclusive* are noop without corresponding min/max if (property.minimum != null || property.maximum != null) property.hasValidation = true; } else if (ModelUtils.isBooleanSchema(p)) { // boolean type property.isBoolean = true; property.getter = toBooleanGetter(name); } else if (ModelUtils.isDateSchema(p)) { // date format property.isString = false; // for backward compatibility with 2.x property.isDate = true; } else if (ModelUtils.isDateTimeSchema(p)) { // date-time format property.isString = false; // for backward compatibility with 2.x property.isDateTime = true; } else if (ModelUtils.isStringSchema(p)) { if (ModelUtils.isByteArraySchema(p)) { property.isByteArray = true; } else if (ModelUtils.isBinarySchema(p)) { property.isBinary = true; property.isFile = true; // file = binary in OAS3 } else if (ModelUtils.isFileSchema(p)) { property.isFile = true; } else if (ModelUtils.isUUIDSchema(p)) { // keep isString to true to make it backward compatible property.isString = true; property.isUuid = true; } else if (ModelUtils.isURISchema(p)) { property.isString = true; // for backward compatibility property.isUri = true; } else if (ModelUtils.isEmailSchema(p)) { property.isString = true; property.isEmail = true; } else { property.isString = true; } property.maxLength = p.getMaxLength(); property.minLength = p.getMinLength(); property.pattern = toRegularExpression(p.getPattern()); // check if any validation rule defined if (property.pattern != null || property.minLength != null || property.maxLength != null) property.hasValidation = true; } else if (ModelUtils.isNumberSchema(p)) { property.isNumeric = Boolean.TRUE; if (ModelUtils.isFloatSchema(p)) { // float property.isFloat = Boolean.TRUE; } else if (ModelUtils.isDoubleSchema(p)) { // double property.isDouble = Boolean.TRUE; } else { // type is number and without format property.isNumber = Boolean.TRUE; } if (p.getMinimum() != null) { property.minimum = String.valueOf(p.getMinimum()); } if (p.getMaximum() != null) { property.maximum = String.valueOf(p.getMaximum()); } if (p.getExclusiveMinimum() != null) { property.exclusiveMinimum = p.getExclusiveMinimum(); } if (p.getExclusiveMaximum() != null) { property.exclusiveMaximum = p.getExclusiveMaximum(); } if (p.getMultipleOf() != null) { property.multipleOf = p.getMultipleOf(); } // check if any validation rule defined // exclusive* are noop without corresponding min/max if (property.minimum != null || property.maximum != null) property.hasValidation = true; } else if (ModelUtils.isFreeFormObject(p)) { property.isFreeFormObject = true; } else if (ModelUtils.isArraySchema(p)) { // default to string if inner item is undefined ArraySchema arraySchema = (ArraySchema) p; Schema innerSchema = ModelUtils.unaliasSchema(this.openAPI, getSchemaItems(arraySchema), importMapping); } else if (ModelUtils.isMapSchema(p)) { Schema innerSchema = ModelUtils.unaliasSchema(this.openAPI, ModelUtils.getAdditionalProperties(p), importMapping); if (innerSchema == null) { LOGGER.error("Undefined map inner type for `{}`. Default to String.", p.getName()); innerSchema = new StringSchema().description("//TODO automatically added by openapi-generator due to undefined type"); p.setAdditionalProperties(innerSchema); } } //Inline enum case: if (p.getEnum() != null && !p.getEnum().isEmpty()) { List _enum = p.getEnum(); property._enum = new ArrayList(); for (Object i : _enum) { property._enum.add(String.valueOf(i)); } property.isEnum = true; Map allowableValues = new HashMap(); allowableValues.put("values", _enum); if (allowableValues.size() > 0) { property.allowableValues = allowableValues; } } Schema referencedSchema = ModelUtils.getReferencedSchema(this.openAPI, p); //Referenced enum case: if (referencedSchema.getEnum() != null && !referencedSchema.getEnum().isEmpty()) { List _enum = referencedSchema.getEnum(); Map allowableValues = new HashMap(); allowableValues.put("values", _enum); if (allowableValues.size() > 0) { property.allowableValues = allowableValues; } } if (referencedSchema.getNullable() != null) { property.isNullable = referencedSchema.getNullable(); } property.dataType = getTypeDeclaration(p); property.dataFormat = p.getFormat(); property.baseType = getSchemaType(p); // this can cause issues for clients which don't support enums if (property.isEnum) { property.datatypeWithEnum = toEnumName(property); property.enumName = toEnumName(property); } else { property.datatypeWithEnum = property.dataType; } if (ModelUtils.isArraySchema(p)) { property.isContainer = true; property.isListContainer = true; property.containerType = "array"; property.baseType = getSchemaType(p); if (p.getXml() != null) { property.isXmlWrapped = p.getXml().getWrapped() == null ? false : p.getXml().getWrapped(); property.xmlPrefix = p.getXml().getPrefix(); property.xmlNamespace = p.getXml().getNamespace(); property.xmlName = p.getXml().getName(); } // handle inner property property.maxItems = p.getMaxItems(); property.minItems = p.getMinItems(); String itemName = null; if (p.getExtensions() != null && p.getExtensions().get("x-item-name") != null) { itemName = p.getExtensions().get("x-item-name").toString(); } if (itemName == null) { itemName = property.name; } ArraySchema arraySchema = (ArraySchema) p; Schema innerSchema = ModelUtils.unaliasSchema(this.openAPI, getSchemaItems(arraySchema), importMapping); CodegenProperty cp = fromProperty(itemName, innerSchema); updatePropertyForArray(property, cp); } else if (ModelUtils.isMapSchema(p)) { property.isContainer = true; property.isMapContainer = true; property.containerType = "map"; property.baseType = getSchemaType(p); property.minItems = p.getMinProperties(); property.maxItems = p.getMaxProperties(); // handle inner property Schema innerSchema = ModelUtils.unaliasSchema(this.openAPI, ModelUtils.getAdditionalProperties(p), importMapping); if (innerSchema == null) { LOGGER.error("Undefined map inner type for `{}`. Default to String.", p.getName()); innerSchema = new StringSchema().description("//TODO automatically added by openapi-generator due to undefined type"); p.setAdditionalProperties(innerSchema); } CodegenProperty cp = fromProperty("inner", innerSchema); updatePropertyForMap(property, cp); } else if (ModelUtils.isFreeFormObject(p)) { property.isFreeFormObject = true; property.baseType = getSchemaType(p); } else { // model // TODO revise the logic below //if (StringUtils.isNotBlank(p.get$ref())) { // property.baseType = getSimpleRef(p.get$ref()); //} // --END of revision setNonArrayMapProperty(property, type); Schema refOrCurrent = ModelUtils.getReferencedSchema(this.openAPI, p); property.isModel = (ModelUtils.isComposedSchema(refOrCurrent) || ModelUtils.isObjectSchema(refOrCurrent)) && ModelUtils.isModel(refOrCurrent); } LOGGER.debug("debugging from property return: " + property); return property; } /** * Update property for array(list) container * * @param property Codegen property * @param innerProperty Codegen inner property of map or list */ protected void updatePropertyForArray(CodegenProperty property, CodegenProperty innerProperty) { if (innerProperty == null) { LOGGER.warn("skipping invalid array property " + Json.pretty(property)); return; } property.dataFormat = innerProperty.dataFormat; if (!languageSpecificPrimitives.contains(innerProperty.baseType)) { property.complexType = innerProperty.baseType; } else { property.isPrimitiveType = true; } property.items = innerProperty; property.mostInnerItems = getMostInnerItems(innerProperty); // inner item is Enum if (isPropertyInnerMostEnum(property)) { // isEnum is set to true when the type is an enum // or the inner type of an array/map is an enum property.isEnum = true; // update datatypeWithEnum and default value for array // e.g. List => List updateDataTypeWithEnumForArray(property); // set allowable values to enum values (including array/map of enum) property.allowableValues = getInnerEnumAllowableValues(property); } } /** * Update property for map container * * @param property Codegen property * @param innerProperty Codegen inner property of map or list */ protected void updatePropertyForMap(CodegenProperty property, CodegenProperty innerProperty) { if (innerProperty == null) { LOGGER.warn("skipping invalid map property " + Json.pretty(property)); return; } if (!languageSpecificPrimitives.contains(innerProperty.baseType)) { property.complexType = innerProperty.baseType; } else { property.isPrimitiveType = true; } property.items = innerProperty; property.mostInnerItems = getMostInnerItems(innerProperty); property.dataFormat = innerProperty.dataFormat; // inner item is Enum if (isPropertyInnerMostEnum(property)) { // isEnum is set to true when the type is an enum // or the inner type of an array/map is an enum property.isEnum = true; // update datatypeWithEnum and default value for map // e.g. Dictionary => Dictionary updateDataTypeWithEnumForMap(property); // set allowable values to enum values (including array/map of enum) property.allowableValues = getInnerEnumAllowableValues(property); } } /** * Update property for map container * * @param property Codegen property * @return True if the inner most type is enum */ protected Boolean isPropertyInnerMostEnum(CodegenProperty property) { CodegenProperty currentProperty = getMostInnerItems(property); return currentProperty == null ? false : currentProperty.isEnum; } protected CodegenProperty getMostInnerItems(CodegenProperty property) { CodegenProperty currentProperty = property; while (currentProperty != null && (Boolean.TRUE.equals(currentProperty.isMapContainer) || Boolean.TRUE.equals(currentProperty.isListContainer))) { 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.isMapContainer) || Boolean.TRUE.equals(baseItem.isListContainer))) { 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.isMapContainer) || Boolean.TRUE.equals(baseItem.isListContainer))) { baseItem = baseItem.items; } if (baseItem != null) { // set both datatype and datetypeWithEnum as only the inner type is enum property.datatypeWithEnum = property.datatypeWithEnum.replace(", " + baseItem.baseType, ", " + toEnumName(baseItem)); // naming the enum with respect to the language enum naming convention // e.g. remove [], {} from array/map of enum property.enumName = toEnumName(property); // set default value for variable with inner enum if (property.defaultValue != null) { property.defaultValue = property.defaultValue.replace(", " + property.items.baseType, ", " + toEnumName(property.items)); } updateCodegenPropertyEnum(property); } } protected void setNonArrayMapProperty(CodegenProperty property, String type) { property.isContainer = false; if (languageSpecificPrimitives().contains(type)) { property.isPrimitiveType = true; } else { property.complexType = property.baseType; property.isModel = true; } } /** * Override with any special handling of response codes * * @param responses OAS Operation's responses * @return default method response or null if not found */ protected ApiResponse findMethodResponse(ApiResponses responses) { String code = null; for (String responseCode : responses.keySet()) { if (responseCode.startsWith("2") || responseCode.equals("default")) { if (code == null || code.compareTo(responseCode) > 0) { code = responseCode; } } } if (code == null) { return null; } return responses.get(code); } /** * Set op's returnBaseType, returnType, examples etc. * * @param operation endpoint Operation * @param schemas a map of the schemas in the openapi spec * @param op endpoint CodegenOperation * @param methodResponse the default ApiResponse for the endpoint */ protected void handleMethodResponse(Operation operation, Map schemas, CodegenOperation op, ApiResponse methodResponse) { handleMethodResponse(operation, schemas, op, methodResponse, Collections.emptyMap()); } /** * Set op's returnBaseType, returnType, examples etc. * * @param operation endpoint Operation * @param schemas a map of the schemas in the openapi spec * @param op endpoint CodegenOperation * @param methodResponse the default ApiResponse for the endpoint * @param importMappings mappings of external types to be omitted by unaliasing */ protected void handleMethodResponse(Operation operation, Map schemas, CodegenOperation op, ApiResponse methodResponse, Map importMappings) { Schema responseSchema = ModelUtils.unaliasSchema(this.openAPI, ModelUtils.getSchemaFromResponse(methodResponse), importMappings); if (responseSchema != null) { CodegenProperty cm = fromProperty("response", responseSchema); if (ModelUtils.isArraySchema(responseSchema)) { ArraySchema as = (ArraySchema) responseSchema; CodegenProperty innerProperty = fromProperty("response", getSchemaItems(as)); op.returnBaseType = innerProperty.baseType; } else if (ModelUtils.isMapSchema(responseSchema)) { CodegenProperty innerProperty = fromProperty("response", ModelUtils.getAdditionalProperties(responseSchema)); op.returnBaseType = innerProperty.baseType; } else { if (cm.complexType != null) { op.returnBaseType = cm.complexType; } else { op.returnBaseType = cm.baseType; } } // generate examples String exampleStatusCode = "200"; for (String key : operation.getResponses().keySet()) { if (operation.getResponses().get(key) == methodResponse && !key.equals("default")) { exampleStatusCode = key; } } op.examples = new ExampleGenerator(schemas, this.openAPI).generateFromResponseSchema(exampleStatusCode, responseSchema, getProducesInfo(this.openAPI, operation)); op.defaultResponse = toDefaultValue(responseSchema); op.returnType = cm.dataType; op.hasReference = schemas.containsKey(op.returnBaseType); // lookup discriminator Schema 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.isMapContainer = true; } else if ("list".equalsIgnoreCase(cm.containerType)) { op.isListContainer = true; } else if ("array".equalsIgnoreCase(cm.containerType)) { op.isListContainer = true; } } else { op.returnSimpleType = true; } if (languageSpecificPrimitives().contains(op.returnBaseType) || op.returnBaseType == null) { op.returnTypeIsPrimitive = true; } } addHeaders(methodResponse, op.responseHeaders); } /** * Convert OAS Operation object to Codegen Operation object * * @param httpMethod HTTP method * @param operation OAS operation object * @param path the path of the operation * @param servers list of servers * @return Codegen Operation object */ public CodegenOperation fromOperation(String path, String httpMethod, Operation operation, List servers) { LOGGER.debug("fromOperation => operation: " + operation); if (operation == null) throw new RuntimeException("operation cannot be null in fromOperation"); Map schemas = ModelUtils.getSchemas(this.openAPI); CodegenOperation op = CodegenModelFactory.newInstance(CodegenModelType.OPERATION); Set imports = new HashSet(); if (operation.getExtensions() != null && !operation.getExtensions().isEmpty()) { op.vendorExtensions.putAll(operation.getExtensions()); Object isCallbackRequest = op.vendorExtensions.remove("x-callback-request"); op.isCallbackRequest = Boolean.TRUE.equals(isCallbackRequest); } // servers setting if (operation.getServers() != null && !operation.getServers().isEmpty()) { // use operation-level servers first if defined op.servers = fromServers(operation.getServers()); } else if (servers != null && !servers.isEmpty()) { // use path-level servers op.servers = fromServers(servers); } // store the original operationId for plug-in op.operationIdOriginal = operation.getOperationId(); String operationId = getOrGenerateOperationId(operation, path, httpMethod); // remove prefix in operationId if (removeOperationIdPrefix) { int offset = operationId.indexOf('_'); if (offset > -1) { operationId = operationId.substring(offset + 1); } } operationId = removeNonNameElementToCamelCase(operationId); if (isStrictSpecBehavior() && !path.startsWith("/")) { // modifies an operation.path to strictly conform to OpenAPI Spec op.path = "/" + path; } else { op.path = path; } op.operationId = toOperationId(operationId); op.summary = escapeText(operation.getSummary()); op.unescapedNotes = operation.getDescription(); op.notes = escapeText(operation.getDescription()); op.hasConsumes = false; op.hasProduces = false; if (operation.getDeprecated() != null) { op.isDeprecated = operation.getDeprecated(); } addConsumesInfo(operation, op); if (operation.getResponses() != null && !operation.getResponses().isEmpty()) { ApiResponse methodResponse = findMethodResponse(operation.getResponses()); for (String key : operation.getResponses().keySet()) { ApiResponse response = operation.getResponses().get(key); addProducesInfo(response, op); CodegenResponse r = fromResponse(key, response); r.hasMore = true; if (r.baseType != null && !defaultIncludes.contains(r.baseType) && !languageSpecificPrimitives.contains(r.baseType)) { imports.add(r.baseType); } r.isDefault = response == methodResponse; op.responses.add(r); if (Boolean.TRUE.equals(r.isBinary) && Boolean.TRUE.equals(r.isDefault)) { op.isResponseBinary = Boolean.TRUE; } if (Boolean.TRUE.equals(r.isFile) && Boolean.TRUE.equals(r.isDefault)) { op.isResponseFile = Boolean.TRUE; } } op.responses.sort((a, b) -> { int aDefault = "0".equals(a.code) ? 1 : 0; int bDefault = "0".equals(b.code) ? 1 : 0; return aDefault - bDefault; }); op.responses.get(op.responses.size() - 1).hasMore = false; if (methodResponse != null) { handleMethodResponse(operation, schemas, op, methodResponse, importMapping); } } if (operation.getCallbacks() != null && !operation.getCallbacks().isEmpty()) { operation.getCallbacks().forEach((name, callback) -> { CodegenCallback c = fromCallback(name, callback, servers); c.hasMore = true; op.callbacks.add(c); }); op.callbacks.get(op.callbacks.size() - 1).hasMore = false; } 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(); CodegenParameter bodyParam = null; RequestBody requestBody = operation.getRequestBody(); if (requestBody != null) { String contentType = getContentType(requestBody); if (contentType != null && (contentType.toLowerCase(Locale.ROOT).startsWith("application/x-www-form-urlencoded") || contentType.toLowerCase(Locale.ROOT).startsWith("multipart"))) { // process form parameters formParams = fromRequestBodyToFormParameters(requestBody, imports); op.isMultipart = contentType.toLowerCase(Locale.ROOT).startsWith("multipart"); for (CodegenParameter cp : formParams) { 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 requestBody = ModelUtils.getReferencedRequestBody(this.openAPI, requestBody); String bodyParameterName = ""; if (op.vendorExtensions != null && op.vendorExtensions.containsKey("x-codegen-request-body-name")) { bodyParameterName = (String) op.vendorExtensions.get("x-codegen-request-body-name"); } bodyParam = fromRequestBody(requestBody, imports, bodyParameterName); bodyParam.description = escapeText(requestBody.getDescription()); postProcessParameter(bodyParam); bodyParams.add(bodyParam); if (prependFormOrBodyParameters) { allParams.add(bodyParam); } // add example if (schemas != null) { 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); // ensure unique params if (ensureUniqueParams) { if (!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 " + p.baseType + " for " + p.baseName); } } } // add form/body parameter (if any) to the end of all parameter list if (!prependFormOrBodyParameters) { for (CodegenParameter cp : formParams) { allParams.add(cp.copy()); } for (CodegenParameter cp : bodyParams) { 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; } } // 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) { Collections.sort(allParams, new Comparator() { @Override public int compare(CodegenParameter one, CodegenParameter another) { if (one.required == another.required) return 0; else if (one.required) return -1; else return 1; } }); } op.allParams = addHasMore(allParams); op.bodyParams = addHasMore(bodyParams); op.pathParams = addHasMore(pathParams); op.queryParams = addHasMore(queryParams); op.headerParams = addHasMore(headerParams); op.cookieParams = addHasMore(cookieParams); op.formParams = addHasMore(formParams); op.requiredParams = addHasMore(requiredParams); op.optionalParams = addHasMore(optionalParams); op.externalDocs = operation.getExternalDocs(); // legacy support op.nickname = op.operationId; if (op.allParams.size() > 0) { op.hasParams = true; } op.hasRequiredParams = op.requiredParams.size() > 0; // 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 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 (response.getContent() != null && response.getContent().size() > 0) { // Ensure validation properties from a target schema are persisted on CodegenResponse. // This ignores any edge case where different schemas have different validations because we don't // have a way to indicate a preference for response schema and are effective 1:1. Schema contentSchema = null; for (MediaType mt : response.getContent().values()) { if (contentSchema != null) break; contentSchema = mt.getSchema(); } if (contentSchema != null) { ModelUtils.syncValidationProperties(contentSchema, r); } } if ("default".equals(responseCode)) { r.code = "0"; } else { r.code = responseCode; } Schema responseSchema; if (this.openAPI != null && this.openAPI.getComponents() != null) { responseSchema = ModelUtils.unaliasSchema(this.openAPI, ModelUtils.getSchemaFromResponse(response), importMapping); } else { // no model/alias defined responseSchema = ModelUtils.getSchemaFromResponse(response); } r.schema = responseSchema; if (responseSchema != null && responseSchema.getPattern() != null) { r.setPattern(toRegularExpression(responseSchema.getPattern())); } 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) { Map allSchemas = null; CodegenProperty cp = fromProperty("response", responseSchema); if (ModelUtils.isArraySchema(responseSchema)) { ArraySchema as = (ArraySchema) responseSchema; CodegenProperty innerProperty = fromProperty("response", getSchemaItems(as)); CodegenProperty innerCp = innerProperty; while (innerCp != null) { r.baseType = innerCp.baseType; innerCp = innerCp.items; } } else { if (cp.complexType != null) { r.baseType = cp.complexType; r.isModel = true; } else { r.baseType = cp.baseType; } } r.dataType = cp.dataType; if (Boolean.TRUE.equals(cp.isString) && Boolean.TRUE.equals(cp.isEmail)) { r.isEmail = true; } else if (Boolean.TRUE.equals(cp.isString) && Boolean.TRUE.equals(cp.isUuid)) { r.isUuid = true; } else if (Boolean.TRUE.equals(cp.isByteArray)) { r.isByteArray = true; } else if (Boolean.TRUE.equals(cp.isString)) { r.isString = true; } else if (Boolean.TRUE.equals(cp.isBoolean)) { r.isBoolean = true; } else if (Boolean.TRUE.equals(cp.isLong)) { r.isLong = true; r.isNumeric = true; } else if (Boolean.TRUE.equals(cp.isInteger)) { r.isInteger = true; r.isNumeric = true; } else if (Boolean.TRUE.equals(cp.isNumber)) { r.isNumber = true; r.isNumeric = true; } else if (Boolean.TRUE.equals(cp.isDouble)) { r.isDouble = true; r.isNumeric = true; } else if (Boolean.TRUE.equals(cp.isFloat)) { r.isFloat = true; r.isNumeric = true; } else if (Boolean.TRUE.equals(cp.isBinary)) { r.isFile = true; // file = binary in OAS3 r.isBinary = true; } else if (Boolean.TRUE.equals(cp.isFile)) { r.isFile = true; } else if (Boolean.TRUE.equals(cp.isDate)) { r.isDate = true; } else if (Boolean.TRUE.equals(cp.isDateTime)) { r.isDateTime = true; } else if (Boolean.TRUE.equals(cp.isFreeFormObject)) { r.isFreeFormObject = true; } else { LOGGER.debug("Property type is not primitive: " + cp.dataType); } if (cp.isContainer) { r.simpleType = false; r.containerType = cp.containerType; r.isMapContainer = "map".equals(cp.containerType); r.isListContainer = "list".equalsIgnoreCase(cp.containerType) || "array".equalsIgnoreCase(cp.containerType); } else { r.simpleType = true; } r.primitiveType = (r.baseType == null || languageSpecificPrimitives().contains(r.baseType)); } if (r.baseType == null) { r.isMapContainer = false; r.isListContainer = false; r.primitiveType = true; r.simpleType = true; } 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; u.hasMore = true; 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(); 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); }); if (!u.requests.isEmpty()) { u.requests.get(u.requests.size() - 1).hasMore = false; } c.urls.add(u); }); if (!c.urls.isEmpty()) { c.urls.get(c.urls.size() - 1).hasMore = false; } return c; } /** * 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); if (parameter.getContent() != null && parameter.getContent().size() > 0) { // Ensure validation properties from a target schema are persisted on CodegenParameter. // This ignores any edge case where different schemas have different validations because we don't // have a way to indicate a preference for parameter schema and are effective 1:1. Schema contentSchema = null; for (MediaType mt : parameter.getContent().values()) { if (contentSchema != null) break; contentSchema = mt.getSchema(); } if (contentSchema != null) { ModelUtils.syncValidationProperties(contentSchema, codegenParameter); } } codegenParameter.baseName = parameter.getName(); codegenParameter.description = escapeText(parameter.getDescription()); codegenParameter.unescapedDescription = parameter.getDescription(); if (parameter.getRequired() != null) { codegenParameter.required = parameter.getRequired(); } 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()); } Schema s; if (parameter.getSchema() != null) { s = parameter.getSchema(); } else if (parameter.getContent() != null) { Content content = parameter.getContent(); if (content.size() > 1) { LOGGER.warn("Multiple schemas found in content, returning only the first one"); } MediaType mediaType = content.values().iterator().next(); s = mediaType.getSchema(); } else { s = null; } if (s != null) { Schema parameterSchema = ModelUtils.unaliasSchema(this.openAPI, s, importMapping); if (parameterSchema == null) { LOGGER.warn("warning! Schema not found for parameter \"" + parameter.getName() + "\", using String"); parameterSchema = new StringSchema().description("//TODO automatically added by openapi-generator due to missing type definition."); } if (Boolean.TRUE.equals(parameterSchema.getNullable())) { // use nullable defined in the spec codegenParameter.isNullable = true; } // set default value codegenParameter.defaultValue = toDefaultValue(parameterSchema); if (parameter.getStyle() != null) { codegenParameter.style = parameter.getStyle().toString(); } // 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 ? false : parameter.getExplode(); // TODO revise collectionFormat String collectionFormat = null; if (ModelUtils.isArraySchema(parameterSchema)) { // for array parameter final ArraySchema arraySchema = (ArraySchema) parameterSchema; Schema inner = getSchemaItems(arraySchema); collectionFormat = getCollectionFormat(parameter); // default to csv: collectionFormat = StringUtils.isEmpty(collectionFormat) ? "csv" : collectionFormat; CodegenProperty codegenProperty = fromProperty("inner", inner); codegenParameter.items = codegenProperty; codegenParameter.mostInnerItems = codegenProperty.mostInnerItems; codegenParameter.baseType = codegenProperty.dataType; codegenParameter.isContainer = true; codegenParameter.isListContainer = true; // recursively add import while (codegenProperty != null) { imports.add(codegenProperty.baseType); codegenProperty = codegenProperty.items; } } else if (ModelUtils.isMapSchema(parameterSchema)) { // for map parameter CodegenProperty codegenProperty = fromProperty("inner", ModelUtils.getAdditionalProperties(parameterSchema)); codegenParameter.items = codegenProperty; codegenParameter.mostInnerItems = codegenProperty.mostInnerItems; codegenParameter.baseType = codegenProperty.dataType; codegenParameter.isContainer = true; codegenParameter.isMapContainer = true; // recursively add import while (codegenProperty != null) { imports.add(codegenProperty.baseType); codegenProperty = codegenProperty.items; } } /* TODO revise the logic below } else { Map args = new HashMap(); String format = qp.getFormat(); args.put(PropertyId.ENUM, qp.getEnum()); property = PropertyBuilder.build(type, format, args); } */ CodegenProperty codegenProperty = fromProperty(parameter.getName(), parameterSchema); // TODO revise below which seems not working //if (parameterSchema.getRequired() != null && !parameterSchema.getRequired().isEmpty() && parameterSchema.getRequired().contains(codegenProperty.baseName)) { codegenProperty.required = Boolean.TRUE.equals(parameter.getRequired()) ? true : false; //} //codegenProperty.required = true; // set boolean flag (e.g. isString) setParameterBooleanFlagWithCodegenProperty(codegenParameter, codegenProperty); String parameterDataType = this.getParameterDataType(parameter, parameterSchema); if (parameterDataType != null) { codegenParameter.dataType = parameterDataType; } else { codegenParameter.dataType = codegenProperty.dataType; } if (ModelUtils.isObjectSchema(parameterSchema)) { codegenProperty.complexType = codegenParameter.dataType; } codegenParameter.dataFormat = codegenProperty.dataFormat; codegenParameter.required = codegenProperty.required; if (codegenProperty.isEnum) { codegenParameter.datatypeWithEnum = codegenProperty.datatypeWithEnum; codegenParameter.enumName = codegenProperty.enumName; } // enum updateCodegenPropertyEnum(codegenProperty); codegenParameter.isEnum = codegenProperty.isEnum; codegenParameter._enum = codegenProperty._enum; codegenParameter.allowableValues = codegenProperty.allowableValues; 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()); // import if (codegenProperty.complexType != null) { imports.add(codegenProperty.complexType); } // validation // handle maximum, minimum properly for int/long by removing the trailing ".0" if (ModelUtils.isIntegerSchema(parameterSchema)) { codegenParameter.maximum = parameterSchema.getMaximum() == null ? null : String.valueOf(parameterSchema.getMaximum().longValue()); codegenParameter.minimum = parameterSchema.getMinimum() == null ? null : String.valueOf(parameterSchema.getMinimum().longValue()); } else { codegenParameter.maximum = parameterSchema.getMaximum() == null ? null : String.valueOf(parameterSchema.getMaximum()); codegenParameter.minimum = parameterSchema.getMinimum() == null ? null : String.valueOf(parameterSchema.getMinimum()); } codegenParameter.exclusiveMaximum = parameterSchema.getExclusiveMaximum() == null ? false : parameterSchema.getExclusiveMaximum(); codegenParameter.exclusiveMinimum = parameterSchema.getExclusiveMinimum() == null ? false : parameterSchema.getExclusiveMinimum(); codegenParameter.maxLength = parameterSchema.getMaxLength(); codegenParameter.minLength = parameterSchema.getMinLength(); codegenParameter.pattern = toRegularExpression(parameterSchema.getPattern()); codegenParameter.maxItems = parameterSchema.getMaxItems(); codegenParameter.minItems = parameterSchema.getMinItems(); codegenParameter.uniqueItems = parameterSchema.getUniqueItems() == null ? false : parameterSchema.getUniqueItems(); codegenParameter.multipleOf = parameterSchema.getMultipleOf(); // exclusive* are noop without corresponding min/max if (codegenParameter.maximum != null || codegenParameter.minimum != null || codegenParameter.maxLength != null || codegenParameter.minLength != null || codegenParameter.maxItems != null || codegenParameter.minItems != null || codegenParameter.pattern != null) { codegenParameter.hasValidation = true; } } else { LOGGER.error("ERROR! Not handling " + parameter + " as Body Parameter at the moment"); /* TODO need to revise the logic below to handle body parameter if (!(parameter instanceof BodyParameter)) { LOGGER.error("Cannot use Parameter " + parameter + " as Body Parameter"); } BodyParameter bp = (BodyParameter) param; Model model = bp.getSchema(); if (model instanceof ModelImpl) { ModelImpl impl = (ModelImpl) model; CodegenModel cm = fromModel(bp.getName(), impl); if (!cm.emptyVars) { codegen.dataType = getTypeDeclaration(cm.classname); imports.add(p.dataType); } else { Property prop = PropertyBuilder.build(impl.getType(), impl.getFormat(), null); prop.setRequired(bp.getRequired()); CodegenProperty cp = fromProperty("property", prop); if (cp != null) { p.baseType = cp.baseType; p.dataType = cp.datatype; p.isPrimitiveType = cp.isPrimitiveType; p.isBinary = isDataTypeBinary(cp.datatype); p.isFile = isDataTypeFile(cp.datatype); if (cp.complexType != null) { imports.add(cp.complexType); } } // set boolean flag (e.g. isString) setParameterBooleanFlagWithCodegenProperty(p, cp); } } else if (model instanceof ArrayModel) { // to use the built-in model parsing, we unwrap the ArrayModel // and get a single property from it ArrayModel impl = (ArrayModel) model; // get the single property ArrayProperty ap = new ArrayProperty().items(impl.getItems()); ap.setRequired(param.getRequired()); CodegenProperty cp = fromProperty("inner", ap); if (cp.complexType != null) { imports.add(cp.complexType); } imports.add(cp.baseType); // recursively add import CodegenProperty innerCp = cp; while(innerCp != null) { if(innerCp.complexType != null) { imports.add(innerCp.complexType); } innerCp = innerCp.items; } p.items = cp; p.dataType = cp.datatype; p.baseType = cp.complexType; p.isPrimitiveType = cp.isPrimitiveType; p.isContainer = true; p.isListContainer = true; // set boolean flag (e.g. isString) setParameterBooleanFlagWithCodegenProperty(p, cp); } else { Model sub = bp.getSchema(); if (sub instanceof RefModel) { String name = ((RefModel) sub).getSimpleRef(); name = getAlias(name); if (typeMapping.containsKey(name)) { name = typeMapping.get(name); p.baseType = name; } else { name = toModelName(name); p.baseType = name; if (defaultIncludes.contains(name)) { imports.add(name); } imports.add(name); name = getTypeDeclaration(name); } p.dataType = name; } } p.paramName = toParamName(bp.getName()); */ } if (parameter instanceof QueryParameter || "query".equalsIgnoreCase(parameter.getIn())) { codegenParameter.isQueryParam = true; } 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()); } // 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); postProcessParameter(codegenParameter); LOGGER.debug("debugging codegenParameter return: " + codegenParameter); return codegenParameter; } /** * Returns the data type of a 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) { if (parameter.get$ref() != null) { String refName = ModelUtils.getSimpleRef(parameter.get$ref()); return toModelName(refName); } 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 */ @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); CodegenSecurity cs = CodegenModelFactory.newInstance(CodegenModelType.SECURITY); cs.name = key; cs.type = securityScheme.getType().toString(); cs.isCode = cs.isPassword = cs.isApplication = cs.isImplicit = false; cs.isHttpSignature = false; cs.isBasicBasic = cs.isBasicBearer = false; cs.scheme = securityScheme.getScheme(); if (securityScheme.getExtensions() != null) { cs.vendorExtensions.putAll(securityScheme.getExtensions()); } if (SecurityScheme.Type.APIKEY.equals(securityScheme.getType())) { cs.isBasic = cs.isOAuth = 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) } else if (SecurityScheme.Type.HTTP.equals(securityScheme.getType())) { cs.isKeyInHeader = cs.isKeyInQuery = cs.isKeyInCookie = cs.isApiKey = cs.isOAuth = false; cs.isBasic = true; if ("basic".equals(securityScheme.getScheme())) { cs.isBasicBasic = true; } else if ("bearer".equals(securityScheme.getScheme())) { cs.isBasicBearer = true; cs.bearerFormat = securityScheme.getBearerFormat(); } else if ("signature".equals(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; LOGGER.warn("Security scheme 'HTTP signature' is a draft IETF RFC and subject to change."); } } else if (SecurityScheme.Type.OAUTH2.equals(securityScheme.getType())) { cs.isKeyInHeader = cs.isKeyInQuery = cs.isKeyInCookie = cs.isApiKey = cs.isBasic = false; cs.isOAuth = true; final OAuthFlows flows = securityScheme.getFlows(); if (securityScheme.getFlows() == null) { throw new RuntimeException("missing oauth flow in " + cs.name); } if (flows.getPassword() != null) { setOauth2Info(cs, flows.getPassword()); cs.isPassword = true; cs.flow = "password"; } else if (flows.getImplicit() != null) { setOauth2Info(cs, flows.getImplicit()); cs.isImplicit = true; cs.flow = "implicit"; } else if (flows.getClientCredentials() != null) { setOauth2Info(cs, flows.getClientCredentials()); cs.isApplication = true; cs.flow = "application"; } else if (flows.getAuthorizationCode() != null) { setOauth2Info(cs, flows.getAuthorizationCode()); cs.isCode = true; cs.flow = "accessCode"; } else { throw new RuntimeException("Could not identify any oauth2 flow in " + cs.name); } } codegenSecurities.add(cs); } // sort auth methods to maintain the same order Collections.sort(codegenSecurities, new Comparator() { @Override public int compare(CodegenSecurity one, CodegenSecurity another) { return ObjectUtils.compare(one.name, another.name); } }); // set 'hasMore' Iterator it = codegenSecurities.iterator(); while (it.hasNext()) { final CodegenSecurity security = it.next(); security.hasMore = it.hasNext(); } return codegenSecurities; } 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: " + httpMethod + " " + path + ". Renamed to auto-generated operationId: " + operationId); } return 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> output = new ArrayList>(examples.size()); for (Map.Entry entry : examples.entrySet()) { final Map kv = new HashMap(); kv.put("contentType", entry.getKey()); kv.put("example", entry.getValue()); output.add(kv); } return output; } /** * Add headers to codegen property * * @param response API response * @param properties list of codegen property */ protected void addHeaders(ApiResponse response, List properties) { if (response.getHeaders() != null) { for (Map.Entry headerEntry : response.getHeaders().entrySet()) { String description = headerEntry.getValue().getDescription(); // follow the $ref Header header = ModelUtils.getReferencedHeader(this.openAPI, headerEntry.getValue()); Schema schema; if (header.getSchema() == null) { LOGGER.warn("No schema defined for Header '" + headerEntry.getKey() + "', using a String schema"); schema = new StringSchema(); } else { schema = header.getSchema(); } CodegenProperty cp = fromProperty(headerEntry.getKey(), schema); cp.setDescription(escapeText(description)); cp.setUnescapedDescription(description); properties.add(cp); } } } private static List addHasMore(List objs) { if (objs != null) { for (int i = 0; i < objs.size(); i++) { if (i > 0) { objs.get(i).secondaryParam = true; } if (i < objs.size() - 1) { objs.get(i).hasMore = true; } } } return objs; } /** * Add operation to group * * @param tag name of the tag * @param resourcePath path of the resource * @param operation OAS Operation object * @param co Codegen Operation object * @param operations map of Codegen operations */ @SuppressWarnings("static-method") public void addOperationToGroup(String tag, String resourcePath, Operation operation, CodegenOperation co, Map> operations) { List opList = operations.get(tag); if (opList == null) { opList = new ArrayList(); operations.put(tag, opList); } // check for operationId uniqueness String uniqueName = co.operationId; int counter = 0; for (CodegenOperation op : opList) { if (uniqueName.equals(op.operationId)) { uniqueName = co.operationId + "_" + counter; counter++; } } if (!co.operationId.equals(uniqueName)) { LOGGER.warn("generated unique operationId `" + uniqueName + "`"); } co.operationId = uniqueName; co.operationIdLowerCase = uniqueName.toLowerCase(Locale.ROOT); co.operationIdCamelCase = camelize(uniqueName); co.operationIdSnakeCase = underscore(uniqueName); opList.add(co); co.baseName = tag; } protected void addParentContainer(CodegenModel model, String name, Schema schema) { final CodegenProperty property = fromProperty(name, schema); addImport(model, property.complexType); model.parent = toInstantiationType(schema); final String containerType = property.containerType; final String instantiationType = instantiationTypes.get(containerType); if (instantiationType != null) { addImport(model, instantiationType); } final String mappedType = typeMapping.get(containerType); if (mappedType != null) { addImport(model, mappedType); } } /** * Generate the next name for the given name, i.e. append "2" to the base name if not ending with a number, * otherwise increase the number by 1. For example: * status => status2 * status2 => status3 * myName100 => myName101 * * @param name The base name * @return The next name for the base name */ private static String generateNextName(String name) { Pattern pattern = Pattern.compile("\\d+\\z"); Matcher matcher = pattern.matcher(name); if (matcher.find()) { String numStr = matcher.group(); int num = Integer.parseInt(numStr) + 1; return name.substring(0, name.length() - numStr.length()) + num; } else { return name + "2"; } } protected void addImport(CodegenModel m, String type) { if (type != null && needToImport(type)) { m.imports.add(type); } } /** * Loop through propertiies and unalias the reference if $ref (reference) is defined * * @param properties model properties (schemas) * @return model properties with direct reference to schemas */ private Map unaliasPropertySchema(Map properties) { if (properties != null) { for (String key : properties.keySet()) { properties.put(key, ModelUtils.unaliasSchema(this.openAPI, properties.get(key), importMapping())); } } return properties; } private void addVars(CodegenModel m, Map properties, List required, Map allProperties, List allRequired) { m.hasRequired = false; if (properties != null && !properties.isEmpty()) { m.hasVars = true; m.hasEnums = false; // TODO need to fix as its false in both cases Set mandatory = required == null ? Collections.emptySet() : new TreeSet(required); // update "vars" without parent's properties (all, required) addVars(m, m.vars, properties, mandatory); m.allMandatory = m.mandatory = mandatory; } else { m.emptyVars = true; m.hasVars = false; m.hasEnums = false; // TODO need to fix as its false in both cases } if (allProperties != null) { Set allMandatory = allRequired == null ? Collections.emptySet() : new TreeSet(allRequired); // update "vars" with parent's properties (all, required) addVars(m, m.allVars, allProperties, allMandatory); m.allMandatory = allMandatory; } else { // without parent, allVars and vars are the same m.allVars = m.vars; m.allMandatory = m.mandatory; } // loop through list to update property name with toVarName Set renamedMandatory = new TreeSet(); Iterator mandatoryIterator = m.mandatory.iterator(); while (mandatoryIterator.hasNext()) { renamedMandatory.add(toVarName(mandatoryIterator.next())); } m.mandatory = renamedMandatory; Set renamedAllMandatory = new TreeSet(); Iterator allMandatoryIterator = m.allMandatory.iterator(); while (allMandatoryIterator.hasNext()) { renamedAllMandatory.add(toVarName(allMandatoryIterator.next())); } m.allMandatory = renamedAllMandatory; } /** * Add variables (properties) to codegen model (list of properties, various flags, etc) * * @param m Codegen model * @param vars list of codegen properties (e.g. vars, allVars) to be updated with the new properties * @param properties a map of properties (schema) * @param mandatory a set of required properties' name */ private void addVars(CodegenModel m, List vars, Map properties, Set mandatory) { for (Map.Entry entry : properties.entrySet()) { final String key = entry.getKey(); final Schema prop = entry.getValue(); if (prop == null) { LOGGER.warn("Please report the issue. There shouldn't be null property for " + key); } else { final CodegenProperty cp = fromProperty(key, prop); cp.required = mandatory.contains(key); m.hasRequired = m.hasRequired || cp.required; m.hasOptional = m.hasOptional || !cp.required; if (cp.isEnum) { // FIXME: if supporting inheritance, when called a second time for allProperties it is possible for // m.hasEnums to be set incorrectly if allProperties has enumerations but properties does not. m.hasEnums = true; } // set model's hasOnlyReadOnly to false if the property is read-only if (!Boolean.TRUE.equals(cp.isReadOnly)) { m.hasOnlyReadOnly = false; } // TODO revise the logic to include map if (cp.isContainer) { addImport(m, typeMapping.get("array")); } addImport(m, cp.baseType); CodegenProperty innerCp = cp; while (innerCp != null) { addImport(m, innerCp.complexType); innerCp = innerCp.items; } vars.add(cp); // if required, add to the list "requiredVars" if (Boolean.TRUE.equals(cp.required)) { m.requiredVars.add(cp); } else { // else add to the list "optionalVars" for optional property m.optionalVars.add(cp); } // if readonly, add to readOnlyVars (list of properties) if (Boolean.TRUE.equals(cp.isReadOnly)) { m.readOnlyVars.add(cp); } else { // else add to readWriteVars (list of properties) // duplicated properties will be removed by removeAllDuplicatedProperty later m.readWriteVars.add(cp); } } } } /** * Determine all of the types in the model definitions (schemas) that are aliases of * simple types. * * @param schemas The complete set of model definitions (schemas). * @return A mapping from model name to type alias */ Map getAllAliases(Map schemas) { if (schemas == null || schemas.isEmpty()) { return new HashMap<>(); } Map aliases = new HashMap<>(); for (Map.Entry entry : schemas.entrySet()) { Schema schema = entry.getValue(); if (isAliasOfSimpleTypes(schema)) { String oasName = entry.getKey(); String schemaType = getPrimitiveType(schema); aliases.put(oasName, schemaType); } } return aliases; } private static Boolean isAliasOfSimpleTypes(Schema schema) { return (!ModelUtils.isObjectSchema(schema) && !ModelUtils.isArraySchema(schema) && !ModelUtils.isMapSchema(schema) && !ModelUtils.isComposedSchema(schema) && schema.getEnum() == null); } /** * Remove characters not suitable for variable or method name from the input and camelize it * * @param name string to be camelize * @return camelized string */ @SuppressWarnings("static-method") public String removeNonNameElementToCamelCase(String name) { return removeNonNameElementToCamelCase(name, "[-_:;#]"); } /** * Remove characters that is not good to be included in method name from the input and camelize it * * @param name string to be camelize * @param nonNameElementPattern a regex pattern of the characters that is not good to be included in name * @return camelized string */ protected String removeNonNameElementToCamelCase(final String name, final String nonNameElementPattern) { String result = Arrays.stream(name.split(nonNameElementPattern)) .map(StringUtils::capitalize) .collect(Collectors.joining("")); if (result.length() > 0) { result = result.substring(0, 1).toLowerCase(Locale.ROOT) + result.substring(1); } return result; } public String apiFilename(String templateName, String tag) { String suffix = apiTemplateFiles().get(templateName); return apiFileFolder() + File.separator + toApiFilename(tag) + suffix; } /** * Return the full path and API documentation file * * @param templateName template name * @param tag tag * @return the API documentation file name with full path */ public String apiDocFilename(String templateName, String tag) { String docExtension = getDocExtension(); String suffix = docExtension != null ? docExtension : apiDocTemplateFiles().get(templateName); return apiDocFileFolder() + File.separator + toApiDocFilename(tag) + suffix; } /** * Return the full path and API test file * * @param templateName template name * @param tag tag * @return the API test file name with full path */ public String apiTestFilename(String templateName, String tag) { String suffix = apiTestTemplateFiles().get(templateName); return apiTestFileFolder() + File.separator + toApiTestFilename(tag) + suffix; } public boolean shouldOverwrite(String filename) { return !(skipOverwrite && new File(filename).exists()); } public boolean isSkipOverwrite() { return skipOverwrite; } public void setSkipOverwrite(boolean skipOverwrite) { this.skipOverwrite = skipOverwrite; } public boolean isRemoveOperationIdPrefix() { return removeOperationIdPrefix; } public void setRemoveOperationIdPrefix(boolean removeOperationIdPrefix) { this.removeOperationIdPrefix = removeOperationIdPrefix; } public boolean isHideGenerationTimestamp() { return hideGenerationTimestamp; } public void setHideGenerationTimestamp(boolean hideGenerationTimestamp) { this.hideGenerationTimestamp = hideGenerationTimestamp; } /** * All library templates supported. * (key: library name, value: library description) * * @return the supported libraries */ public Map supportedLibraries() { return supportedLibraries; } /** * Set library template (sub-template). * * @param library Library template */ public void setLibrary(String library) { if (library != null && !supportedLibraries.containsKey(library)) { StringBuilder sb = new StringBuilder("Unknown library: " + library + "\nAvailable libraries:"); if (supportedLibraries.size() == 0) { sb.append("\n ").append("NONE"); } else { for (String lib : supportedLibraries.keySet()) { sb.append("\n ").append(lib); } } throw new RuntimeException(sb.toString()); } this.library = library; } /** * Library template (sub-template). * * @return Library template */ public String getLibrary() { return library; } /** * Set Git host. * * @param gitHost Git host */ public void setGitHost(String gitHost) { this.gitHost = gitHost; } /** * Git host. * * @return Git host */ public String getGitHost() { return gitHost; } /** * Set Git user ID. * * @param gitUserId Git user ID */ public void setGitUserId(String gitUserId) { this.gitUserId = gitUserId; } /** * Git user ID * * @return Git user ID */ public String getGitUserId() { return gitUserId; } /** * Set Git repo ID. * * @param gitRepoId Git repo ID */ public void setGitRepoId(String gitRepoId) { this.gitRepoId = gitRepoId; } /** * Git repo ID * * @return Git repo ID */ public String getGitRepoId() { return gitRepoId; } /** * Set release note. * * @param releaseNote Release note */ public void setReleaseNote(String releaseNote) { this.releaseNote = releaseNote; } /** * Release note * * @return Release note */ public String getReleaseNote() { return releaseNote; } /** * Documentation files extension * * @return Documentation files extension */ public String getDocExtension() { return docExtension; } /** * Set Documentation files extension * * @param userDocExtension documentation files extension */ public void setDocExtension(String userDocExtension) { this.docExtension = userDocExtension; } /** * Set HTTP user agent. * * @param httpUserAgent HTTP user agent */ public void setHttpUserAgent(String httpUserAgent) { this.httpUserAgent = httpUserAgent; } /** * HTTP user agent * * @return HTTP user agent */ public String getHttpUserAgent() { return httpUserAgent; } @SuppressWarnings("static-method") protected CliOption buildLibraryCliOption(Map supportedLibraries) { StringBuilder sb = new StringBuilder("library template (sub-template) to use:"); for (String lib : supportedLibraries.keySet()) { sb.append("\n").append(lib).append(" - ").append(supportedLibraries.get(lib)); } return new CliOption("library", sb.toString()); } /** * Sanitize name (parameter, property, method, etc) * * @param name string to be sanitize * @return sanitized string */ @SuppressWarnings("static-method") public String sanitizeName(String name) { return sanitizeName(name, "\\W"); } @Override public void setTemplatingEngine(TemplatingEngineAdapter templatingEngine) { this.templatingEngine = templatingEngine; } @Override public TemplatingEngineAdapter getTemplatingEngine() { return this.templatingEngine; } /** * Sanitize name (parameter, property, method, etc) * * @param name string to be sanitize * @param removeCharRegEx a regex containing all char that will be removed * @return sanitized string */ public String sanitizeName(String name, String removeCharRegEx) { return sanitizeName(name, removeCharRegEx, new ArrayList()); } /** * Sanitize name (parameter, property, method, etc) * * @param name string to be sanitize * @param removeCharRegEx a regex containing all char that will be removed * @param exceptionList a list of matches which should not be sanitized (i.e exception) * @return sanitized string */ @SuppressWarnings("static-method") public String sanitizeName(final String name, String removeCharRegEx, ArrayList exceptionList) { // NOTE: performance wise, we should have written with 2 replaceAll to replace desired // character with _ or empty character. Below aims to spell out different cases we've // encountered so far and hopefully make it easier for others to add more special // cases in the future. // better error handling when map/array type is invalid if (name == null) { LOGGER.error("String to be sanitized is null. Default to ERROR_UNKNOWN"); return "ERROR_UNKNOWN"; } // if the name is just '$', map it to 'value' for the time being. if ("$".equals(name)) { return "value"; } SanitizeNameOptions opts = new SanitizeNameOptions(name, removeCharRegEx, exceptionList); return sanitizedNameCache.get(opts, sanitizeNameOptions -> { String modifiable = sanitizeNameOptions.getName(); List exceptions = sanitizeNameOptions.getExceptions(); // input[] => input modifiable = this.sanitizeValue(modifiable, "\\[\\]", "", exceptions); // input[a][b] => input_a_b modifiable = this.sanitizeValue(modifiable, "\\[", "_", exceptions); modifiable = this.sanitizeValue(modifiable, "\\]", "", exceptions); // input(a)(b) => input_a_b modifiable = this.sanitizeValue(modifiable, "\\(", "_", exceptions); modifiable = this.sanitizeValue(modifiable, "\\)", "", exceptions); // input.name => input_name modifiable = this.sanitizeValue(modifiable, "\\.", "_", exceptions); // input-name => input_name modifiable = this.sanitizeValue(modifiable, "-", "_", exceptions); // a|b => a_b modifiable = this.sanitizeValue(modifiable, "\\|", "_", exceptions); // input name and age => input_name_and_age modifiable = this.sanitizeValue(modifiable, " ", "_", exceptions); // /api/films/get => _api_films_get // \api\films\get => _api_films_get modifiable = modifiable.replaceAll("/", "_"); modifiable = modifiable.replaceAll("\\\\", "_"); // remove everything else other than word, number and _ // $php_variable => php_variable if (allowUnicodeIdentifiers) { //could be converted to a single line with ?: operator modifiable = Pattern.compile(sanitizeNameOptions.getRemoveCharRegEx(), Pattern.UNICODE_CHARACTER_CLASS).matcher(modifiable).replaceAll(""); } else { modifiable = modifiable.replaceAll(sanitizeNameOptions.getRemoveCharRegEx(), ""); } return modifiable; }); } private String sanitizeValue(String value, String replaceMatch, String replaceValue, List exceptionList) { if (exceptionList.size() == 0 || !exceptionList.contains(replaceMatch)) { return value.replaceAll(replaceMatch, replaceValue); } return value; } /** * Sanitize tag * * @param tag Tag * @return Sanitized tag */ public String sanitizeTag(String tag) { tag = camelize(sanitizeName(tag)); // tag starts with numbers if (tag.matches("^\\d.*")) { tag = "Class" + tag; } return tag; } /** * Only write if the file doesn't exist * * @param outputFolder Output folder * @param supportingFile Supporting file */ public void writeOptional(String outputFolder, SupportingFile supportingFile) { String folder = ""; if (outputFolder != null && !"".equals(outputFolder)) { folder += outputFolder + File.separator; } folder += supportingFile.folder; if (!"".equals(folder)) { folder += File.separator + supportingFile.destinationFilename; } else { folder = supportingFile.destinationFilename; } if (!new File(folder).exists()) { supportingFiles.add(supportingFile); } else { LOGGER.info("Skipped overwriting " + supportingFile.destinationFilename + " as the file already exists in " + folder); } } /** * Set CodegenParameter boolean flag using CodegenProperty. * * @param parameter Codegen Parameter * @param property Codegen property */ public void setParameterBooleanFlagWithCodegenProperty(CodegenParameter parameter, CodegenProperty property) { if (parameter == null) { LOGGER.error("Codegen Parameter cannot be null."); return; } if (property == null) { LOGGER.error("Codegen Property cannot be null."); return; } if (Boolean.TRUE.equals(property.isEmail) && Boolean.TRUE.equals(property.isString)) { parameter.isEmail = true; } else if (Boolean.TRUE.equals(property.isUuid) && Boolean.TRUE.equals(property.isString)) { parameter.isUuid = true; } else if (Boolean.TRUE.equals(property.isByteArray)) { parameter.isByteArray = true; parameter.isPrimitiveType = true; } else if (Boolean.TRUE.equals(property.isBinary)) { parameter.isBinary = true; parameter.isPrimitiveType = true; } else if (Boolean.TRUE.equals(property.isString)) { parameter.isString = true; parameter.isPrimitiveType = true; } else if (Boolean.TRUE.equals(property.isBoolean)) { parameter.isBoolean = true; parameter.isPrimitiveType = true; } else if (Boolean.TRUE.equals(property.isLong)) { parameter.isLong = true; parameter.isPrimitiveType = true; } else if (Boolean.TRUE.equals(property.isInteger)) { parameter.isInteger = true; parameter.isPrimitiveType = true; } else if (Boolean.TRUE.equals(property.isDouble)) { parameter.isDouble = true; parameter.isPrimitiveType = true; } else if (Boolean.TRUE.equals(property.isFloat)) { parameter.isFloat = true; parameter.isPrimitiveType = true; } else if (Boolean.TRUE.equals(property.isNumber)) { parameter.isNumber = true; parameter.isPrimitiveType = true; } else if (Boolean.TRUE.equals(property.isDate)) { parameter.isDate = true; parameter.isPrimitiveType = true; } else if (Boolean.TRUE.equals(property.isDateTime)) { parameter.isDateTime = true; parameter.isPrimitiveType = true; } else if (Boolean.TRUE.equals(property.isFreeFormObject)) { parameter.isFreeFormObject = true; } else { LOGGER.debug("Property type is not primitive: " + property.dataType); } if (Boolean.TRUE.equals(property.isFile)) { parameter.isFile = true; } if (Boolean.TRUE.equals(property.isModel)) { parameter.isModel = true; } } /** * Update codegen property's enum by adding "enumVars" (with name and value) * * @param var list of CodegenProperty */ public void updateCodegenPropertyEnum(CodegenProperty var) { Map allowableValues = var.allowableValues; // handle array if (var.mostInnerItems != null) { allowableValues = var.mostInnerItems.allowableValues; } if (allowableValues == null) { return; } List values = (List) allowableValues.get("values"); if (values == null) { return; } String varDataType = var.mostInnerItems != null ? var.mostInnerItems.dataType : var.dataType; Optional referencedSchema = ModelUtils.getSchemas(openAPI).entrySet().stream() .filter(entry -> Objects.equals(varDataType, toModelName(entry.getKey()))) .map(Map.Entry::getValue) .findFirst(); String dataType = (referencedSchema.isPresent()) ? getTypeDeclaration(referencedSchema.get()) : varDataType; List> enumVars = buildEnumVars(values, dataType); // if "x-enum-varnames" or "x-enum-descriptions" defined, update varnames Map extensions = var.mostInnerItems != null ? var.mostInnerItems.getVendorExtensions() : var.getVendorExtensions(); if (referencedSchema.isPresent()) { extensions = referencedSchema.get().getExtensions(); } updateEnumVarsWithExtensions(enumVars, extensions); allowableValues.put("enumVars", enumVars); // handle default value for enum, e.g. available => StatusEnum.AVAILABLE if (var.defaultValue != null) { String enumName = null; final String enumDefaultValue; if ("string".equalsIgnoreCase(dataType)) { enumDefaultValue = toEnumValue(var.defaultValue, dataType); } else { enumDefaultValue = var.defaultValue; } for (Map enumVar : enumVars) { if (enumDefaultValue.equals(enumVar.get("value"))) { enumName = (String) enumVar.get("name"); break; } } if (enumName != null) { var.defaultValue = toEnumDefaultValue(enumName, var.datatypeWithEnum); } } } protected List> buildEnumVars(List values, String dataType) { List> enumVars = new ArrayList<>(); int truncateIdx = 0; if (isRemoveEnumValuePrefix()) { String commonPrefix = findCommonPrefixOfVars(values); truncateIdx = commonPrefix.length(); } for (Object value : values) { Map enumVar = new HashMap<>(); String enumName; if (truncateIdx == 0) { enumName = String.valueOf(value); } else { enumName = value.toString().substring(truncateIdx); if ("".equals(enumName)) { enumName = value.toString(); } } enumVar.put("name", toEnumVarName(enumName, dataType)); enumVar.put("value", toEnumValue(String.valueOf(value), dataType)); enumVar.put("isString", isDataTypeString(dataType)); enumVars.add(enumVar); } return enumVars; } protected void updateEnumVarsWithExtensions(List> enumVars, Map vendorExtensions) { if (vendorExtensions != null) { updateEnumVarsWithExtensions(enumVars, vendorExtensions, "x-enum-varnames", "name"); updateEnumVarsWithExtensions(enumVars, vendorExtensions, "x-enum-descriptions", "enumDescription"); } } private void updateEnumVarsWithExtensions(List> enumVars, Map vendorExtensions, String extensionKey, String key) { if (vendorExtensions.containsKey(extensionKey)) { List values = (List) vendorExtensions.get(extensionKey); int size = Math.min(enumVars.size(), values.size()); for (int i = 0; i < size; i++) { enumVars.get(i).put(key, values.get(i)); } } } /** * If the pattern misses the delimiter, add "/" to the beginning and end * Otherwise, return the original pattern * * @param pattern the pattern (regular expression) * @return the pattern with delimiter */ public String addRegularExpressionDelimiter(String pattern) { if (StringUtils.isEmpty(pattern)) { return pattern; } if (!pattern.matches("^/.*")) { return "/" + pattern.replaceAll("/", "\\\\/") + "/"; } return pattern; } /** * reads propertyKey from additionalProperties, converts it to a boolean and * writes it back to additionalProperties to be usable as a boolean in * mustache files. * * @param propertyKey property key * @return property value as boolean */ public boolean convertPropertyToBooleanAndWriteBack(String propertyKey) { boolean result = convertPropertyToBoolean(propertyKey); writePropertyBack(propertyKey, result); return result; } /** * Provides an override location, if any is specified, for the .openapi-generator-ignore. *

* This is originally intended for the first generation only. * * @return a string of the full path to an override ignore file. */ public String getIgnoreFilePathOverride() { return ignoreFilePathOverride; } /** * Sets an override location for the '.openapi-generator-ignore' location for the first code generation. * * @param ignoreFileOverride The full path to an ignore file */ public void setIgnoreFilePathOverride(final String ignoreFileOverride) { this.ignoreFilePathOverride = ignoreFileOverride; } public boolean convertPropertyToBoolean(String propertyKey) { final Object booleanValue = additionalProperties.get(propertyKey); Boolean result = Boolean.FALSE; if (booleanValue instanceof Boolean) { result = (Boolean) booleanValue; } else if (booleanValue instanceof String) { result = Boolean.parseBoolean((String) booleanValue); } else { LOGGER.warn("The value (generator's option) must be either boolean or string. Default to `false`."); } return result; } public void writePropertyBack(String propertyKey, boolean value) { additionalProperties.put(propertyKey, value); } protected String getContentType(RequestBody requestBody) { if (requestBody == null || requestBody.getContent() == null || requestBody.getContent().isEmpty()) { LOGGER.debug("Cannot determine the content type. Returning null."); return null; } return new ArrayList<>(requestBody.getContent().keySet()).get(0); } private void setOauth2Info(CodegenSecurity codegenSecurity, OAuthFlow flow) { codegenSecurity.authorizationUrl = flow.getAuthorizationUrl(); codegenSecurity.tokenUrl = flow.getTokenUrl(); if (flow.getScopes() != null && !flow.getScopes().isEmpty()) { List> scopes = new ArrayList>(); int count = 0, numScopes = flow.getScopes().size(); for (Map.Entry scopeEntry : flow.getScopes().entrySet()) { Map scope = new HashMap(); scope.put("scope", scopeEntry.getKey()); scope.put("description", escapeText(scopeEntry.getValue())); count += 1; if (count < numScopes) { scope.put("hasMore", "true"); } else { scope.put("hasMore", null); } scopes.add(scope); } codegenSecurity.scopes = scopes; } } private void addConsumesInfo(Operation operation, CodegenOperation codegenOperation) { RequestBody requestBody = ModelUtils.getReferencedRequestBody(this.openAPI, operation.getRequestBody()); if (requestBody == null || requestBody.getContent() == null || requestBody.getContent().isEmpty()) { return; } Set consumes = requestBody.getContent().keySet(); List> mediaTypeList = new ArrayList<>(); int count = 0; for (String key : consumes) { Map mediaType = new HashMap<>(); if ("*/*".equals(key)) { // skip as it implies `consumes` in OAS2 is not defined continue; } else { mediaType.put("mediaType", escapeText(escapeQuotationMark(key))); } count += 1; if (count < consumes.size()) { mediaType.put("hasMore", "true"); } else { mediaType.put("hasMore", null); } mediaTypeList.add(mediaType); } if (!mediaTypeList.isEmpty()) { codegenOperation.consumes = mediaTypeList; codegenOperation.hasConsumes = true; } } public static Set getConsumesInfo(OpenAPI openAPI, Operation operation) { RequestBody requestBody = ModelUtils.getReferencedRequestBody(openAPI, operation.getRequestBody()); if (requestBody == null || requestBody.getContent() == null || requestBody.getContent().isEmpty()) { return Collections.emptySet(); // return empty set } return requestBody.getContent().keySet(); } public boolean hasFormParameter(OpenAPI openAPI, Operation operation) { Set consumesInfo = getConsumesInfo(openAPI, operation); if (consumesInfo == null || consumesInfo.isEmpty()) { return false; } for (String consume : consumesInfo) { if (consume != null && (consume.toLowerCase(Locale.ROOT).startsWith("application/x-www-form-urlencoded") || consume.toLowerCase(Locale.ROOT).startsWith("multipart"))) { return true; } } return false; } public boolean hasBodyParameter(OpenAPI openAPI, Operation operation) { RequestBody requestBody = ModelUtils.getReferencedRequestBody(openAPI, operation.getRequestBody()); if (requestBody == null) { return false; } Schema schema = ModelUtils.getSchemaFromRequestBody(requestBody); return ModelUtils.getReferencedSchema(openAPI, schema) != null; } private void addProducesInfo(ApiResponse inputResponse, CodegenOperation codegenOperation) { ApiResponse response = ModelUtils.getReferencedApiResponse(this.openAPI, inputResponse); if (response == null || response.getContent() == null || response.getContent().isEmpty()) { return; } Set produces = response.getContent().keySet(); if (codegenOperation.produces == null) { codegenOperation.produces = new ArrayList<>(); } Set existingMediaTypes = new HashSet<>(); for (Map mediaType : codegenOperation.produces) { existingMediaTypes.add(mediaType.get("mediaType")); } int count = 0; for (String key : produces) { // escape quotation to avoid code injection, "*/*" is a special case, do nothing String encodedKey = "*/*".equals(key) ? key : escapeText(escapeQuotationMark(key)); //Only unique media types should be added to "produces" if (!existingMediaTypes.contains(encodedKey)) { Map mediaType = new HashMap(); mediaType.put("mediaType", encodedKey); count += 1; if (count < produces.size()) { mediaType.put("hasMore", "true"); } else { mediaType.put("hasMore", null); } if (!codegenOperation.produces.isEmpty()) { final Map lastMediaType = codegenOperation.produces.get(codegenOperation.produces.size() - 1); lastMediaType.put("hasMore", "true"); } codegenOperation.produces.add(mediaType); codegenOperation.hasProduces = Boolean.TRUE; } } } /** * returns the list of MIME types the APIs can produce * * @param openAPI current specification instance * @param operation Operation * @return a set of MIME types */ public static Set getProducesInfo(final OpenAPI openAPI, final Operation operation) { if (operation.getResponses() == null || operation.getResponses().isEmpty()) { return null; } Set produces = new TreeSet(); for (ApiResponse r : operation.getResponses().values()) { ApiResponse response = ModelUtils.getReferencedApiResponse(openAPI, r); if (response.getContent() != null) { produces.addAll(response.getContent().keySet()); } } return produces; } protected String getCollectionFormat(Parameter parameter) { if (Parameter.StyleEnum.FORM.equals(parameter.getStyle())) { // Ref: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#style-values if (Boolean.TRUE.equals(parameter.getExplode())) { // explode is true (default) return "multi"; } else { return "csv"; } } else if (Parameter.StyleEnum.SIMPLE.equals(parameter.getStyle())) { return "csv"; } else if (Parameter.StyleEnum.PIPEDELIMITED.equals(parameter.getStyle())) { return "pipe"; } else if (Parameter.StyleEnum.SPACEDELIMITED.equals(parameter.getStyle())) { return "space"; } else { return null; } } public CodegenType getTag() { return null; } public String getName() { return null; } public String getHelp() { return null; } public List fromRequestBodyToFormParameters(RequestBody body, Set imports) { List parameters = new ArrayList(); LOGGER.debug("debugging fromRequestBodyToFormParameters= " + body); Schema schema = ModelUtils.getSchemaFromRequestBody(body); schema = ModelUtils.getReferencedSchema(this.openAPI, schema); if (schema.getProperties() != null && !schema.getProperties().isEmpty()) { Map properties = schema.getProperties(); for (Map.Entry entry : properties.entrySet()) { CodegenParameter codegenParameter = CodegenModelFactory.newInstance(CodegenModelType.PARAMETER); // key => property name // value => property schema String collectionFormat = null; Schema s = entry.getValue(); // array of schema if (ModelUtils.isArraySchema(s)) { final ArraySchema arraySchema = (ArraySchema) s; Schema inner = getSchemaItems(arraySchema); codegenParameter = fromFormProperty(entry.getKey(), inner, imports); CodegenProperty codegenProperty = fromProperty("inner", inner); codegenParameter.items = codegenProperty; codegenParameter.mostInnerItems = codegenProperty.mostInnerItems; codegenParameter.baseType = codegenProperty.dataType; codegenParameter.isPrimitiveType = false; codegenParameter.isContainer = true; codegenParameter.isListContainer = true; codegenParameter.description = escapeText(s.getDescription()); codegenParameter.dataType = getTypeDeclaration(arraySchema); if (codegenParameter.baseType != null && codegenParameter.enumName != null) { codegenParameter.datatypeWithEnum = codegenParameter.dataType.replace(codegenParameter.baseType, codegenParameter.enumName); } else { LOGGER.warn("Could not compute datatypeWithEnum from " + codegenParameter.baseType + ", " + codegenParameter.enumName); } //TODO fix collectformat for form parameters //collectionFormat = getCollectionFormat(s); // default to csv: codegenParameter.collectionFormat = StringUtils.isEmpty(collectionFormat) ? "csv" : collectionFormat; // set nullable setParameterNullable(codegenParameter, codegenProperty); // recursively add import while (codegenProperty != null) { imports.add(codegenProperty.baseType); codegenProperty = codegenProperty.items; } } else if (ModelUtils.isMapSchema(s)) { LOGGER.error("Map of form parameters not supported. Please report the issue to https://github.com/openapitools/openapi-generator if you need help."); continue; } else { codegenParameter = fromFormProperty(entry.getKey(), entry.getValue(), imports); } // Set 'required' flag defined in the schema element if (!codegenParameter.required && schema.getRequired() != null) { codegenParameter.required = schema.getRequired().contains(entry.getKey()); } parameters.add(codegenParameter); } } return parameters; } public CodegenParameter fromFormProperty(String name, Schema propertySchema, Set imports) { CodegenParameter codegenParameter = CodegenModelFactory.newInstance(CodegenModelType.PARAMETER); LOGGER.debug("Debugging fromFormProperty {}: {}", name, propertySchema); CodegenProperty codegenProperty = fromProperty(name, propertySchema); ModelUtils.syncValidationProperties(propertySchema, codegenProperty); codegenParameter.isFormParam = Boolean.TRUE; codegenParameter.baseName = codegenProperty.baseName; codegenParameter.paramName = toParamName((codegenParameter.baseName)); codegenParameter.baseType = codegenProperty.baseType; codegenParameter.dataType = codegenProperty.dataType; codegenParameter.dataFormat = codegenProperty.dataFormat; codegenParameter.description = escapeText(codegenProperty.description); codegenParameter.unescapedDescription = codegenProperty.getDescription(); codegenParameter.jsonSchema = Json.pretty(propertySchema); codegenParameter.defaultValue = codegenProperty.getDefaultValue(); if (codegenProperty.getVendorExtensions() != null && !codegenProperty.getVendorExtensions().isEmpty()) { codegenParameter.vendorExtensions = codegenProperty.getVendorExtensions(); } if (propertySchema.getRequired() != null && !propertySchema.getRequired().isEmpty() && propertySchema.getRequired().contains(codegenProperty.baseName)) { codegenParameter.required = Boolean.TRUE; } // non-array/map updateCodegenPropertyEnum(codegenProperty); codegenParameter.isEnum = codegenProperty.isEnum; codegenParameter._enum = codegenProperty._enum; codegenParameter.allowableValues = codegenProperty.allowableValues; if (codegenProperty.isEnum) { codegenParameter.datatypeWithEnum = codegenProperty.datatypeWithEnum; codegenParameter.enumName = codegenProperty.enumName; } if (codegenProperty.items != null && codegenProperty.items.isEnum) { codegenParameter.items = codegenProperty.items; codegenParameter.mostInnerItems = codegenProperty.mostInnerItems; } // import if (codegenProperty.complexType != null) { imports.add(codegenProperty.complexType); } // validation // handle maximum, minimum properly for int/long by removing the trailing ".0" if (ModelUtils.isIntegerSchema(propertySchema)) { codegenParameter.maximum = propertySchema.getMaximum() == null ? null : String.valueOf(propertySchema.getMaximum().longValue()); codegenParameter.minimum = propertySchema.getMinimum() == null ? null : String.valueOf(propertySchema.getMinimum().longValue()); } else { codegenParameter.maximum = propertySchema.getMaximum() == null ? null : String.valueOf(propertySchema.getMaximum()); codegenParameter.minimum = propertySchema.getMinimum() == null ? null : String.valueOf(propertySchema.getMinimum()); } codegenParameter.exclusiveMaximum = propertySchema.getExclusiveMaximum() == null ? false : propertySchema.getExclusiveMaximum(); codegenParameter.exclusiveMinimum = propertySchema.getExclusiveMinimum() == null ? false : propertySchema.getExclusiveMinimum(); codegenParameter.maxLength = propertySchema.getMaxLength(); codegenParameter.minLength = propertySchema.getMinLength(); codegenParameter.pattern = toRegularExpression(propertySchema.getPattern()); codegenParameter.maxItems = propertySchema.getMaxItems(); codegenParameter.minItems = propertySchema.getMinItems(); codegenParameter.uniqueItems = propertySchema.getUniqueItems() == null ? false : propertySchema.getUniqueItems(); codegenParameter.multipleOf = propertySchema.getMultipleOf(); // exclusive* are noop without corresponding min/max if (codegenParameter.maximum != null || codegenParameter.minimum != null || codegenParameter.maxLength != null || codegenParameter.minLength != null || codegenParameter.maxItems != null || codegenParameter.minItems != null || codegenParameter.pattern != null) { codegenParameter.hasValidation = true; } setParameterBooleanFlagWithCodegenProperty(codegenParameter, codegenProperty); setParameterExampleValue(codegenParameter); // set nullable setParameterNullable(codegenParameter, codegenProperty); //TODO collectionFormat for form parameter not yet supported //codegenParameter.collectionFormat = getCollectionFormat(propertySchema); return codegenParameter; } public CodegenParameter fromRequestBody(RequestBody body, Set imports, String bodyParameterName) { if (body == null) { LOGGER.error("body in fromRequestBody cannot be null!"); throw new RuntimeException("body in fromRequestBody cannot be null!"); } CodegenParameter codegenParameter = CodegenModelFactory.newInstance(CodegenModelType.PARAMETER); codegenParameter.baseName = "UNKNOWN_BASE_NAME"; codegenParameter.paramName = "UNKNOWN_PARAM_NAME"; codegenParameter.description = escapeText(body.getDescription()); codegenParameter.required = body.getRequired() != null ? body.getRequired() : Boolean.FALSE; codegenParameter.isBodyParam = Boolean.TRUE; String name = null; LOGGER.debug("Request body = " + body); Schema schema = ModelUtils.getSchemaFromRequestBody(body); if (schema == null) { throw new RuntimeException("Request body cannot be null. Possible cause: missing schema in body parameter (OAS v2): " + body); } if (StringUtils.isNotBlank(schema.get$ref())) { name = ModelUtils.getSimpleRef(schema.get$ref()); } schema = ModelUtils.getReferencedSchema(this.openAPI, schema); ModelUtils.syncValidationProperties(schema, codegenParameter); if (ModelUtils.isMapSchema(schema)) { Schema inner = ModelUtils.getAdditionalProperties(schema); if (inner == null) { LOGGER.error("No inner type supplied for map parameter `{}`. Default to type:string", schema.getName()); inner = new StringSchema().description("//TODO automatically added by openapi-generator"); schema.setAdditionalProperties(inner); } CodegenProperty codegenProperty = fromProperty("property", schema); imports.add(codegenProperty.baseType); CodegenProperty innerCp = codegenProperty; while (innerCp != null) { if (innerCp.complexType != null) { imports.add(innerCp.complexType); } innerCp = innerCp.items; } if (StringUtils.isEmpty(bodyParameterName)) { codegenParameter.baseName = "request_body"; } else { codegenParameter.baseName = bodyParameterName; } codegenParameter.paramName = toParamName(codegenParameter.baseName); codegenParameter.items = codegenProperty.items; codegenParameter.mostInnerItems = codegenProperty.mostInnerItems; codegenParameter.dataType = getTypeDeclaration(schema); codegenParameter.baseType = getSchemaType(inner); codegenParameter.isContainer = Boolean.TRUE; codegenParameter.isMapContainer = Boolean.TRUE; setParameterBooleanFlagWithCodegenProperty(codegenParameter, codegenProperty); // set nullable setParameterNullable(codegenParameter, codegenProperty); } else if (ModelUtils.isArraySchema(schema)) { final ArraySchema arraySchema = (ArraySchema) schema; Schema inner = getSchemaItems(arraySchema); CodegenProperty codegenProperty = fromProperty("property", arraySchema); imports.add(codegenProperty.baseType); CodegenProperty innerCp = codegenProperty; CodegenProperty mostInnerItem = innerCp; // loop through multidimensional array to add proper import // also find the most inner item while (innerCp != null) { if (innerCp.complexType != null) { imports.add(innerCp.complexType); } mostInnerItem = innerCp; innerCp = innerCp.items; } if (StringUtils.isEmpty(bodyParameterName)) { if (StringUtils.isEmpty(mostInnerItem.complexType)) { codegenParameter.baseName = "request_body"; } else { codegenParameter.baseName = mostInnerItem.complexType; } } else { codegenParameter.baseName = bodyParameterName; } codegenParameter.paramName = toArrayModelParamName(codegenParameter.baseName); codegenParameter.items = codegenProperty.items; codegenParameter.mostInnerItems = codegenProperty.mostInnerItems; codegenParameter.dataType = getTypeDeclaration(arraySchema); codegenParameter.baseType = getSchemaType(inner); codegenParameter.isContainer = Boolean.TRUE; codegenParameter.isListContainer = Boolean.TRUE; setParameterBooleanFlagWithCodegenProperty(codegenParameter, codegenProperty); // set nullable setParameterNullable(codegenParameter, codegenProperty); while (codegenProperty != null) { imports.add(codegenProperty.baseType); codegenProperty = codegenProperty.items; } } else if (ModelUtils.isFreeFormObject(schema)) { // HTTP request body is free form object CodegenProperty codegenProperty = fromProperty("FREE_FORM_REQUEST_BODY", schema); if (codegenProperty != null) { if (StringUtils.isEmpty(bodyParameterName)) { codegenParameter.baseName = "body"; // default to body } else { codegenParameter.baseName = bodyParameterName; } codegenParameter.isPrimitiveType = true; codegenParameter.baseType = codegenProperty.baseType; codegenParameter.dataType = codegenProperty.dataType; codegenParameter.description = codegenProperty.description; codegenParameter.paramName = toParamName(codegenParameter.baseName); } setParameterBooleanFlagWithCodegenProperty(codegenParameter, codegenProperty); // set nullable setParameterNullable(codegenParameter, codegenProperty); } else if (ModelUtils.isObjectSchema(schema) || ModelUtils.isComposedSchema(schema)) { CodegenModel codegenModel = null; if (StringUtils.isNotBlank(name)) { schema.setName(name); codegenModel = fromModel(name, schema); } if (codegenModel != null) { codegenParameter.isModel = true; } if (codegenModel != null && !codegenModel.emptyVars) { if (StringUtils.isEmpty(bodyParameterName)) { codegenParameter.baseName = codegenModel.classname; } else { codegenParameter.baseName = bodyParameterName; } codegenParameter.paramName = toParamName(codegenParameter.baseName); codegenParameter.baseType = codegenModel.classname; codegenParameter.dataType = getTypeDeclaration(codegenModel.classname); codegenParameter.description = codegenModel.description; imports.add(codegenParameter.baseType); } else { CodegenProperty codegenProperty = fromProperty("property", schema); if (codegenProperty != null && codegenProperty.getComplexType() != null && codegenProperty.getComplexType().contains(" | ")) { List parts = Arrays.asList(codegenProperty.getComplexType().split(" \\| ")); imports.addAll(parts); String codegenModelName = codegenProperty.getComplexType(); codegenParameter.baseName = codegenModelName; codegenParameter.paramName = toParamName(codegenParameter.baseName); codegenParameter.baseType = codegenParameter.baseName; codegenParameter.dataType = getTypeDeclaration(codegenModelName); codegenParameter.description = codegenProperty.getDescription(); } else { if (ModelUtils.getAdditionalProperties(schema) != null) {// http body is map LOGGER.error("Map should be supported. Please report to openapi-generator github repo about the issue."); } else if (codegenProperty != null) { String codegenModelName, codegenModelDescription; if (codegenModel != null) { codegenModelName = codegenModel.classname; codegenModelDescription = codegenModel.description; } else { LOGGER.warn("The following schema has undefined (null) baseType. " + "It could be due to form parameter defined in OpenAPI v2 spec with incorrect consumes. " + "A correct 'consumes' for form parameters should be " + "'application/x-www-form-urlencoded' or 'multipart/?'"); LOGGER.warn("schema: " + schema); LOGGER.warn("codegenModel is null. Default to UNKNOWN_BASE_TYPE"); codegenModelName = "UNKNOWN_BASE_TYPE"; codegenModelDescription = "UNKNOWN_DESCRIPTION"; } if (StringUtils.isEmpty(bodyParameterName)) { codegenParameter.baseName = codegenModelName; } else { codegenParameter.baseName = bodyParameterName; } codegenParameter.paramName = toParamName(codegenParameter.baseName); codegenParameter.baseType = codegenModelName; codegenParameter.dataType = getTypeDeclaration(codegenModelName); codegenParameter.description = codegenModelDescription; imports.add(codegenParameter.baseType); if (codegenProperty.complexType != null) { imports.add(codegenProperty.complexType); } } } setParameterBooleanFlagWithCodegenProperty(codegenParameter, codegenProperty); // set nullable setParameterNullable(codegenParameter, codegenProperty); } } else { // HTTP request body is primitive type (e.g. integer, string, etc) CodegenProperty codegenProperty = fromProperty("PRIMITIVE_REQUEST_BODY", schema); if (codegenProperty != null) { if (StringUtils.isEmpty(bodyParameterName)) { codegenParameter.baseName = "body"; // default to body } else { codegenParameter.baseName = bodyParameterName; } codegenParameter.isPrimitiveType = true; codegenParameter.baseType = codegenProperty.baseType; codegenParameter.dataType = codegenProperty.dataType; codegenParameter.description = codegenProperty.description; codegenParameter.paramName = toParamName(codegenParameter.baseName); codegenParameter.minimum = codegenProperty.minimum; codegenParameter.maximum = codegenProperty.maximum; codegenParameter.exclusiveMinimum = codegenProperty.exclusiveMinimum; codegenParameter.exclusiveMaximum = codegenProperty.exclusiveMaximum; codegenParameter.minLength = codegenProperty.minLength; codegenParameter.maxLength = codegenProperty.maxLength; codegenParameter.pattern = codegenProperty.pattern; if (codegenProperty.complexType != null) { imports.add(codegenProperty.complexType); } } setParameterBooleanFlagWithCodegenProperty(codegenParameter, codegenProperty); // set nullable setParameterNullable(codegenParameter, codegenProperty); } // set the parameter's example value // should be overridden by lang codegen setParameterExampleValue(codegenParameter, body); return codegenParameter; } protected void addOption(String key, String description, String defaultValue) { CliOption option = new CliOption(key, description); if (defaultValue != null) option.defaultValue(defaultValue); cliOptions.add(option); } protected void updateOption(String key, String defaultValue) { for (CliOption cliOption : cliOptions) { if (cliOption.getOpt().equals(key)) { cliOption.setDefault(defaultValue); break; } } } protected void removeOption(String key) { for (int i = 0; i < cliOptions.size(); i++) { if (key.equals(cliOptions.get(i).getOpt())) { cliOptions.remove(i); break; } } } protected void addSwitch(String key, String description, Boolean defaultValue) { CliOption option = CliOption.newBoolean(key, description); if (defaultValue != null) option.defaultValue(defaultValue.toString()); cliOptions.add(option); } /** * generates OpenAPI specification file in JSON format * * @param objs map of object */ protected void generateJSONSpecFile(Map objs) { OpenAPI openAPI = (OpenAPI) objs.get("openAPI"); if (openAPI != null) { objs.put("openapi-json", SerializerUtils.toJsonString(openAPI)); } } /** * generates OpenAPI specification file in YAML format * * @param objs map of object */ public void generateYAMLSpecFile(Map objs) { OpenAPI openAPI = (OpenAPI) objs.get("openAPI"); String yaml = SerializerUtils.toYamlString(openAPI); if (yaml != null) { objs.put("openapi-yaml", yaml); } } /** * checks if the data should be classified as "string" in enum * e.g. double in C# needs to be double-quoted (e.g. "2.8") by treating it as a string * In the future, we may rename this function to "isEnumString" * * @param dataType data type * @return true if it's a enum string */ public boolean isDataTypeString(String dataType) { return "String".equals(dataType); } @Override public List fromServers(List servers) { if (servers == null) { return Collections.emptyList(); } List codegenServers = new LinkedList<>(); for (Server server : servers) { CodegenServer cs = new CodegenServer(); cs.description = escapeText(server.getDescription()); cs.url = server.getUrl(); cs.variables = this.fromServerVariables(server.getVariables()); codegenServers.add(cs); } return codegenServers; } @Override public List fromServerVariables(Map variables) { if (variables == null) { return Collections.emptyList(); } Map variableOverrides = serverVariableOverrides(); List codegenServerVariables = new LinkedList<>(); for (Entry variableEntry : variables.entrySet()) { CodegenServerVariable codegenServerVariable = new CodegenServerVariable(); ServerVariable variable = variableEntry.getValue(); List enums = variable.getEnum(); codegenServerVariable.defaultValue = variable.getDefault(); codegenServerVariable.description = escapeText(variable.getDescription()); codegenServerVariable.enumValues = enums; codegenServerVariable.name = variableEntry.getKey(); // Sets the override value for a server variable pattern. // NOTE: OpenAPI Specification doesn't prevent multiple server URLs with variables. If multiple objects have the same // variables pattern, user overrides will apply to _all_ of these patterns. We may want to consider indexed overrides. if (variableOverrides != null && !variableOverrides.isEmpty()) { String value = variableOverrides.getOrDefault(variableEntry.getKey(), variable.getDefault()); codegenServerVariable.value = value; if (enums != null && !enums.isEmpty() && !enums.contains(value)) { LOGGER.warn("Variable override of '{}' is not listed in the enum of allowed values ({}).", value, StringUtils.join(enums, ",")); } } else { codegenServerVariable.value = variable.getDefault(); } codegenServerVariables.add(codegenServerVariable); } return codegenServerVariables; } private void setParameterNullable(CodegenParameter parameter, CodegenProperty property) { if (parameter == null || property == null) { return; } parameter.isNullable = property.isNullable; } /** * Post-process the auto-generated file, e.g. using go-fmt to format the Go code. The file type can be "model-test", * "model-doc", "model", "api", "api-test", "api-doc", "supporting-mustache", "supporting-common", * "openapi-generator-ignore", "openapi-generator-version" *

* TODO: store these values in enum instead * * @param file file to be processed * @param fileType file type */ public void postProcessFile(File file, String fileType) { LOGGER.debug("Post processing file {} ({})", file, fileType); } /** * Boolean value indicating the state of the option for post-processing file using envirionment variables. * * @return true if the option is enabled */ public boolean isEnablePostProcessFile() { return enablePostProcessFile; } /** * Set the boolean value indicating the state of the option for post-processing file using envirionment variables. * * @param enablePostProcessFile true to enable post-processing file */ public void setEnablePostProcessFile(boolean enablePostProcessFile) { this.enablePostProcessFile = enablePostProcessFile; } /** * Get the boolean value indicating the state of the option for updating only changed files */ @Override public boolean isEnableMinimalUpdate() { return enableMinimalUpdate; } /** * Set the boolean value indicating the state of the option for updating only changed files * * @param enableMinimalUpdate true to enable minimal update */ @Override public void setEnableMinimalUpdate(boolean enableMinimalUpdate) { this.enableMinimalUpdate = enableMinimalUpdate; } /** * Indicates whether the codegen configuration should treat documents as strictly defined by the OpenAPI specification. * * @return true to act strictly upon spec documents, potentially modifying the spec to strictly fit the spec. */ @Override public boolean isStrictSpecBehavior() { return this.strictSpecBehavior; } /** * Sets the boolean valid indicating whether generation will work strictly against the specification, potentially making * minor changes to the input document. * * @param strictSpecBehavior true if we will behave strictly, false to allow specification documents which pass validation to be loosely interpreted against the spec. */ @Override public void setStrictSpecBehavior(final boolean strictSpecBehavior) { this.strictSpecBehavior = strictSpecBehavior; } @Override public FeatureSet getFeatureSet() { return this.generatorMetadata.getFeatureSet(); } /** * Get the boolean value indicating whether to remove enum value prefixes */ @Override public boolean isRemoveEnumValuePrefix() { return this.removeEnumValuePrefix; } /** * Set the boolean value indicating whether to remove enum value prefixes * * @param removeEnumValuePrefix true to enable enum value prefix removal */ @Override public void setRemoveEnumValuePrefix(final boolean removeEnumValuePrefix) { this.removeEnumValuePrefix = removeEnumValuePrefix; } //// Following methods are related to the "useOneOfInterfaces" feature /** * Add "x-oneOf-name" extension to a given oneOf schema (assuming it has at least 1 oneOf elements) * @param s schema to add the extension to * @param name name of the parent oneOf schema */ public void addOneOfNameExtension(ComposedSchema s, String name) { if (s.getOneOf() != null && s.getOneOf().size() > 0) { s.addExtension("x-oneOf-name", name); } } /** * Add a given ComposedSchema as an interface model to be generated * @param cs ComposedSchema object to create as interface model * @param type name to use for the generated interface model */ public void addOneOfInterfaceModel(ComposedSchema cs, String type) { CodegenModel cm = new CodegenModel(); cm.discriminator = createDiscriminator("", (Schema) cs); for (Schema o : cs.getOneOf()) { if (o.get$ref() == null) { if (cm.discriminator != null && o.get$ref() == null) { // OpenAPI spec states that inline objects should not be considered when discriminator is used // https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#discriminatorObject LOGGER.warn("Ignoring inline object in oneOf definition of {}, since discriminator is used", type); } else { LOGGER.warn("Inline models are not supported in oneOf definition right now"); } continue; } cm.oneOf.add(toModelName(ModelUtils.getSimpleRef(o.get$ref()))); } cm.name = type; cm.classname = type; cm.vendorExtensions.put("x-is-one-of-interface", true); cm.interfaceModels = new ArrayList(); addOneOfInterfaces.add(cm); } public void addImportsToOneOfInterface(List> imports) {} //// End of methods related to the "useOneOfInterfaces" feature protected void modifyFeatureSet(Consumer processor) { FeatureSet.Builder builder = getFeatureSet().modify(); processor.accept(builder); this.generatorMetadata = GeneratorMetadata.newBuilder(generatorMetadata) .featureSet(builder.build()).build(); } private static class SanitizeNameOptions { public SanitizeNameOptions(String name, String removeCharRegEx, List exceptions) { this.name = name; this.removeCharRegEx = removeCharRegEx; if (exceptions != null) { this.exceptions = Collections.unmodifiableList(exceptions); } else { this.exceptions = Collections.unmodifiableList(new ArrayList<>()); } } public String getName() { return name; } public String getRemoveCharRegEx() { return removeCharRegEx; } public List getExceptions() { return exceptions; } private String name; private String removeCharRegEx; private List exceptions; @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; SanitizeNameOptions that = (SanitizeNameOptions) o; return Objects.equals(getName(), that.getName()) && Objects.equals(getRemoveCharRegEx(), that.getRemoveCharRegEx()) && Objects.equals(getExceptions(), that.getExceptions()); } @Override public int hashCode() { return Objects.hash(getName(), getRemoveCharRegEx(), getExceptions()); } } }