supportingFiles() {
+ return supportingFiles;
+ }
+
+ @Override
+ public String outputFolder() {
+ return outputFolder;
+ }
+
+ @Override
+ public void setOutputDir(String dir) {
+ this.outputFolder = dir;
+ }
+
+ @Override
+ public String getOutputDir() {
+ return outputFolder();
+ }
+
+ @Override
+ public String getInputSpec() {
+ return inputSpec;
+ }
+
+ @Override
+ public void setInputSpec(String inputSpec) {
+ this.inputSpec = inputSpec;
+ }
+
+ @Override
+ public String getFilesMetadataFilename() {
+ return filesMetadataFilename;
+ }
+
+ @Override
+ public String getVersionMetadataFilename() {
+ return versionMetadataFilename;
+ }
+
+ public Boolean getLegacyDiscriminatorBehavior() {
+ return legacyDiscriminatorBehavior;
+ }
+
+ public Boolean getDisallowAdditionalPropertiesIfNotPresent() {
+ return disallowAdditionalPropertiesIfNotPresent;
+ }
+
+ public Boolean getEnumUnknownDefaultCase() {
+ return enumUnknownDefaultCase;
+ }
+
+ public Boolean getUseOneOfInterfaces() {
+ return useOneOfInterfaces;
+ }
+
+ public void setUseOneOfInterfaces(Boolean useOneOfInterfaces) {
+ this.useOneOfInterfaces = useOneOfInterfaces;
+ }
+
+ /**
+ * Return the regular expression/JSON schema pattern (http://json-schema.org/latest/json-schema-validation.html#anchor33)
+ *
+ * @param pattern the pattern (regular expression)
+ * @return properly-escaped pattern
+ */
+ public String toRegularExpression(String pattern) {
+ return addRegularExpressionDelimiter(escapeText(pattern));
+ }
+
+ /**
+ * Return the file name of the Api Test
+ *
+ * @param name the file name of the Api
+ * @return the file name of the Api
+ */
+ @Override
+ public String toApiFilename(String name) {
+ return toApiName(name);
+ }
+
+ /**
+ * Return the file name of the Api Documentation
+ *
+ * @param name the file name of the Api
+ * @return the file name of the Api
+ */
+ @Override
+ public String toApiDocFilename(String name) {
+ return toApiName(name);
+ }
+
+ /**
+ * Return the file name of the Api Test
+ *
+ * @param name the file name of the Api
+ * @return the file name of the Api
+ */
+ @Override
+ public String toApiTestFilename(String name) {
+ return toApiName(name) + "Test";
+ }
+
+ /**
+ * Return the variable name in the Api
+ *
+ * @param name the variable name of the Api
+ * @return the snake-cased variable name
+ */
+ @Override
+ public String toApiVarName(String name) {
+ return lowerCamelCase(name);
+ }
+
+ /**
+ * Return the capitalized file name of the model
+ *
+ * @param name the model name
+ * @return the file name of the model
+ */
+ @Override
+ public String toModelFilename(String name) {
+ return camelize(name);
+ }
+
+ /**
+ * Return the capitalized file name of the model test
+ *
+ * @param name the model name
+ * @return the file name of the model
+ */
+ @Override
+ public String toModelTestFilename(String name) {
+ return camelize(name) + "Test";
+ }
+
+ /**
+ * Return the capitalized file name of the model documentation
+ *
+ * @param name the model name
+ * @return the file name of the model
+ */
+ @Override
+ public String toModelDocFilename(String name) {
+ return camelize(name);
+ }
+
+ /**
+ * Returns metadata about the generator.
+ *
+ * @return A provided {@link GeneratorMetadata} instance
+ */
+ @Override
+ public GeneratorMetadata getGeneratorMetadata() {
+ return generatorMetadata;
+ }
+
+ /**
+ * Return the operation ID (method name)
+ *
+ * @param operationId operation ID
+ * @return the sanitized method name
+ */
+ @SuppressWarnings("static-method")
+ public String toOperationId(String operationId) {
+ // throw exception if method name is empty
+ if (StringUtils.isEmpty(operationId)) {
+ throw new RuntimeException("Empty method name (operationId) not allowed");
+ }
+
+ return operationId;
+ }
+
+ /**
+ * Return the variable name by removing invalid characters and proper escaping if
+ * it's a reserved word.
+ *
+ * @param name the variable name
+ * @return the sanitized variable name
+ */
+ public String toVarName(final String name) {
+ // obtain the name from nameMapping directly if provided
+ if (nameMapping.containsKey(name)) {
+ return nameMapping.get(name);
+ }
+
+ if (reservedWords.contains(name)) {
+ return escapeReservedWord(name);
+ } else if (name.chars().anyMatch(character -> specialCharReplacements.containsKey(String.valueOf((char) character)))) {
+ return escape(name, specialCharReplacements, null, null);
+ }
+ return name;
+ }
+
+ /**
+ * Return the parameter name by removing invalid characters and proper escaping if
+ * it's a reserved word.
+ *
+ * @param name Codegen property object
+ * @return the sanitized parameter name
+ */
+ @Override
+ public String toParamName(String name) {
+ // obtain the name from parameterNameMapping directly if provided
+ if (parameterNameMapping.containsKey(name)) {
+ return parameterNameMapping.get(name);
+ }
+
+ name = removeNonNameElementToCamelCase(name); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'.
+ if (reservedWords.contains(name)) {
+ return escapeReservedWord(name);
+ } else if (name.chars().anyMatch(character -> specialCharReplacements.containsKey(String.valueOf((char) character)))) {
+ return escape(name, specialCharReplacements, null, null);
+ }
+ return name;
+
+ }
+
+ /**
+ * Return the parameter name of array of model
+ *
+ * @param name name of the array model
+ * @return the sanitized parameter name
+ */
+ public String toArrayModelParamName(String name) {
+ return toParamName(name);
+ }
+
+ /**
+ * Return the Enum name (e.g. StatusEnum given 'status')
+ *
+ * @param property Codegen property
+ * @return the Enum name
+ */
+ @SuppressWarnings("static-method")
+ public String toEnumName(CodegenProperty property) {
+ return StringUtils.capitalize(property.name) + "Enum";
+ }
+
+ /**
+ * Return the escaped name of the reserved word
+ *
+ * @param name the name to be escaped
+ * @return the escaped reserved word
+ *
+ * throws Runtime exception as reserved word is not allowed (default behavior)
+ */
+ @Override
+ @SuppressWarnings("static-method")
+ public String escapeReservedWord(String name) {
+ throw new RuntimeException("reserved word " + name + " not allowed");
+ }
+
+ /**
+ * Return the fully-qualified "Model" name for import
+ *
+ * @param name the name of the "Model"
+ * @return the fully-qualified "Model" name for import
+ */
+ @Override
+ public String toModelImport(String name) {
+ if ("".equals(modelPackage())) {
+ return name;
+ } else {
+ return modelPackage() + "." + name;
+ }
+ }
+
+ /**
+ * Returns the same content as [[toModelImport]] with key the fully-qualified Model name and value the initial input.
+ * In case of union types this method has a key for each separate model and import.
+ *
+ * @param name the name of the "Model"
+ * @return Map of fully-qualified models.
+ */
+ @Override
+ public Map toModelImportMap(String name) {
+ return Collections.singletonMap(this.toModelImport(name), name);
+ }
+
+ /**
+ * Return the fully-qualified "Api" name for import
+ *
+ * @param name the name of the "Api"
+ * @return the fully-qualified "Api" name for import
+ */
+ @Override
+ public String toApiImport(String name) {
+ return apiPackage() + "." + name;
+ }
+
+ /**
+ * Default constructor.
+ * This method will map between OAS type and language-specified type, as well as mapping
+ * between OAS type and the corresponding import statement for the language. This will
+ * also add some language specified CLI options, if any.
+ * returns string presentation of the example path (it's a constructor)
+ */
+ public DefaultCodegen() {
+ CodegenType codegenType = getTag();
+ if (codegenType == null) {
+ codegenType = CodegenType.OTHER;
+ }
+
+ generatorMetadata = GeneratorMetadata.newBuilder()
+ .stability(Stability.STABLE)
+ .featureSet(DefaultFeatureSet)
+ .generationMessage(String.format(Locale.ROOT, "OpenAPI Generator: %s (%s)", getName(), codegenType.toValue()))
+ .build();
+
+ defaultIncludes = new HashSet<>(
+ Arrays.asList("double",
+ "int",
+ "long",
+ "short",
+ "char",
+ "float",
+ "String",
+ "boolean",
+ "Boolean",
+ "Double",
+ "Void",
+ "Integer",
+ "Long",
+ "Float")
+ );
+
+ typeMapping = new HashMap<>();
+ typeMapping.put("array", "List");
+ typeMapping.put("set", "Set");
+ typeMapping.put("map", "Map");
+ typeMapping.put("boolean", "Boolean");
+ typeMapping.put("string", "String");
+ typeMapping.put("int", "Integer");
+ typeMapping.put("float", "Float");
+ typeMapping.put("double", "Double");
+ typeMapping.put("number", "BigDecimal");
+ typeMapping.put("decimal", "BigDecimal");
+ typeMapping.put("DateTime", "Date");
+ typeMapping.put("long", "Long");
+ typeMapping.put("short", "Short");
+ typeMapping.put("integer", "Integer");
+ typeMapping.put("UnsignedInteger", "Integer");
+ typeMapping.put("UnsignedLong", "Long");
+ typeMapping.put("char", "String");
+ typeMapping.put("object", "Object");
+ // FIXME: java specific type should be in Java Based Abstract Impl's
+ typeMapping.put("ByteArray", "byte[]");
+ typeMapping.put("binary", "File");
+ typeMapping.put("file", "File");
+ typeMapping.put("UUID", "UUID");
+ typeMapping.put("URI", "URI");
+ typeMapping.put("AnyType", "oas_any_type_not_mapped");
+
+ instantiationTypes = new HashMap<>();
+
+ reservedWords = new HashSet<>();
+
+ cliOptions.add(CliOption.newBoolean(CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG,
+ CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG_DESC).defaultValue(Boolean.TRUE.toString()));
+ cliOptions.add(CliOption.newBoolean(CodegenConstants.SORT_MODEL_PROPERTIES_BY_REQUIRED_FLAG,
+ CodegenConstants.SORT_MODEL_PROPERTIES_BY_REQUIRED_FLAG_DESC).defaultValue(Boolean.TRUE.toString()));
+ cliOptions.add(CliOption.newBoolean(CodegenConstants.ENSURE_UNIQUE_PARAMS, CodegenConstants
+ .ENSURE_UNIQUE_PARAMS_DESC).defaultValue(Boolean.TRUE.toString()));
+ // name formatting options
+ cliOptions.add(CliOption.newBoolean(CodegenConstants.ALLOW_UNICODE_IDENTIFIERS, CodegenConstants
+ .ALLOW_UNICODE_IDENTIFIERS_DESC).defaultValue(Boolean.FALSE.toString()));
+ // option to change the order of form/body parameter
+ cliOptions.add(CliOption.newBoolean(CodegenConstants.PREPEND_FORM_OR_BODY_PARAMETERS,
+ CodegenConstants.PREPEND_FORM_OR_BODY_PARAMETERS_DESC).defaultValue(Boolean.FALSE.toString()));
+
+ // option to change how we process + set the data in the discriminator mapping
+ CliOption legacyDiscriminatorBehaviorOpt = CliOption.newBoolean(CodegenConstants.LEGACY_DISCRIMINATOR_BEHAVIOR, CodegenConstants.LEGACY_DISCRIMINATOR_BEHAVIOR_DESC).defaultValue(Boolean.TRUE.toString());
+ Map legacyDiscriminatorBehaviorOpts = new HashMap<>();
+ legacyDiscriminatorBehaviorOpts.put("true", "The mapping in the discriminator includes descendent schemas that allOf inherit from self and the discriminator mapping schemas in the OAS document.");
+ legacyDiscriminatorBehaviorOpts.put("false", "The mapping in the discriminator includes any descendent schemas that allOf inherit from self, any oneOf schemas, any anyOf schemas, any x-discriminator-values, and the discriminator mapping schemas in the OAS document AND Codegen validates that oneOf and anyOf schemas contain the required discriminator and throws an error if the discriminator is missing.");
+ legacyDiscriminatorBehaviorOpt.setEnum(legacyDiscriminatorBehaviorOpts);
+ cliOptions.add(legacyDiscriminatorBehaviorOpt);
+
+ // option to change how we process + set the data in the 'additionalProperties' keyword.
+ CliOption disallowAdditionalPropertiesIfNotPresentOpt = CliOption.newBoolean(
+ CodegenConstants.DISALLOW_ADDITIONAL_PROPERTIES_IF_NOT_PRESENT,
+ CodegenConstants.DISALLOW_ADDITIONAL_PROPERTIES_IF_NOT_PRESENT_DESC).defaultValue(Boolean.TRUE.toString());
+ Map disallowAdditionalPropertiesIfNotPresentOpts = new HashMap<>();
+ disallowAdditionalPropertiesIfNotPresentOpts.put("false",
+ "The 'additionalProperties' implementation is compliant with the OAS and JSON schema specifications.");
+ disallowAdditionalPropertiesIfNotPresentOpts.put("true",
+ "Keep the old (incorrect) behaviour that 'additionalProperties' is set to false by default.");
+ disallowAdditionalPropertiesIfNotPresentOpt.setEnum(disallowAdditionalPropertiesIfNotPresentOpts);
+ cliOptions.add(disallowAdditionalPropertiesIfNotPresentOpt);
+ this.setDisallowAdditionalPropertiesIfNotPresent(true);
+
+ CliOption enumUnknownDefaultCaseOpt = CliOption.newBoolean(
+ CodegenConstants.ENUM_UNKNOWN_DEFAULT_CASE,
+ CodegenConstants.ENUM_UNKNOWN_DEFAULT_CASE_DESC).defaultValue(Boolean.FALSE.toString());
+ Map enumUnknownDefaultCaseOpts = new HashMap<>();
+ enumUnknownDefaultCaseOpts.put("false",
+ "No changes to the enum's are made, this is the default option.");
+ enumUnknownDefaultCaseOpts.put("true",
+ "With this option enabled, each enum will have a new case, 'unknown_default_open_api', so that when the enum case sent by the server is not known by the client/spec, can safely be decoded to this case.");
+ enumUnknownDefaultCaseOpt.setEnum(enumUnknownDefaultCaseOpts);
+ cliOptions.add(enumUnknownDefaultCaseOpt);
+ this.setEnumUnknownDefaultCase(false);
+
+ // initialize special character mapping
+ initializeSpecialCharacterMapping();
+
+ // Register common Mustache lambdas.
+ registerMustacheLambdas();
+ }
+
+ /**
+ * Initialize special character mapping
+ */
+ protected void initializeSpecialCharacterMapping() {
+ // Initialize special characters
+ specialCharReplacements.put("$", "Dollar");
+ specialCharReplacements.put("^", "Caret");
+ specialCharReplacements.put("|", "Pipe");
+ specialCharReplacements.put("=", "Equal");
+ specialCharReplacements.put("*", "Star");
+ specialCharReplacements.put("-", "Minus");
+ specialCharReplacements.put("&", "Ampersand");
+ specialCharReplacements.put("%", "Percent");
+ specialCharReplacements.put("#", "Hash");
+ specialCharReplacements.put("@", "At");
+ specialCharReplacements.put("!", "Exclamation");
+ specialCharReplacements.put("+", "Plus");
+ specialCharReplacements.put(":", "Colon");
+ specialCharReplacements.put(";", "Semicolon");
+ specialCharReplacements.put(">", "Greater_Than");
+ specialCharReplacements.put("<", "Less_Than");
+ specialCharReplacements.put(".", "Period");
+ specialCharReplacements.put("_", "Underscore");
+ specialCharReplacements.put("?", "Question_Mark");
+ specialCharReplacements.put(",", "Comma");
+ specialCharReplacements.put("'", "Quote");
+ specialCharReplacements.put("\"", "Double_Quote");
+ specialCharReplacements.put("/", "Slash");
+ specialCharReplacements.put("\\", "Back_Slash");
+ specialCharReplacements.put("(", "Left_Parenthesis");
+ specialCharReplacements.put(")", "Right_Parenthesis");
+ specialCharReplacements.put("{", "Left_Curly_Bracket");
+ specialCharReplacements.put("}", "Right_Curly_Bracket");
+ specialCharReplacements.put("[", "Left_Square_Bracket");
+ specialCharReplacements.put("]", "Right_Square_Bracket");
+ specialCharReplacements.put("~", "Tilde");
+ specialCharReplacements.put("`", "Backtick");
+
+ specialCharReplacements.put("<=", "Less_Than_Or_Equal_To");
+ specialCharReplacements.put(">=", "Greater_Than_Or_Equal_To");
+ specialCharReplacements.put("!=", "Not_Equal");
+ specialCharReplacements.put("<>", "Not_Equal");
+ specialCharReplacements.put("~=", "Tilde_Equal");
+ specialCharReplacements.put("==", "Double_Equal");
+ }
+
+ /**
+ * Return the symbol name of a symbol
+ *
+ * @param input Symbol (e.g. $)
+ * @return Symbol name (e.g. Dollar)
+ */
+ protected String getSymbolName(String input) {
+ return specialCharReplacements.get(input);
+ }
+
+ /**
+ * Return the example path
+ *
+ * @param path the path of the operation
+ * @param operation OAS operation object
+ * @return string presentation of the example path
+ */
+ @Override
+ @SuppressWarnings("static-method")
+ public String generateExamplePath(String path, Operation operation) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(path);
+
+ if (operation.getParameters() != null) {
+ int count = 0;
+
+ for (Parameter param : operation.getParameters()) {
+ if (param instanceof QueryParameter) {
+ StringBuilder paramPart = new StringBuilder();
+ QueryParameter qp = (QueryParameter) param;
+
+ if (count == 0) {
+ paramPart.append("?");
+ } else {
+ paramPart.append(",");
+ }
+ count += 1;
+ if (!param.getRequired()) {
+ paramPart.append("[");
+ }
+ paramPart.append(param.getName()).append("=");
+ paramPart.append("{");
+
+ // TODO support for multi, tsv?
+ if (qp.getStyle() != null) {
+ paramPart.append(param.getName()).append("1");
+ if (Parameter.StyleEnum.FORM.equals(qp.getStyle())) {
+ if (qp.getExplode() != null && qp.getExplode()) {
+ paramPart.append(",");
+ } else {
+ paramPart.append("&").append(param.getName()).append("=");
+ paramPart.append(param.getName()).append("2");
+ }
+ } else if (Parameter.StyleEnum.PIPEDELIMITED.equals(qp.getStyle())) {
+ paramPart.append("|");
+ } else if (Parameter.StyleEnum.SPACEDELIMITED.equals(qp.getStyle())) {
+ paramPart.append("%20");
+ } else {
+ LOGGER.warn("query parameter '{}' style not support: {}", param.getName(), qp.getStyle());
+ }
+ } else {
+ paramPart.append(param.getName());
+ }
+
+ paramPart.append("}");
+ if (!param.getRequired()) {
+ paramPart.append("]");
+ }
+ sb.append(paramPart);
+ }
+ }
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Return the instantiation type of the property, especially for map and array
+ *
+ * @param schema property schema
+ * @return string presentation of the instantiation type of the property
+ */
+ public String toInstantiationType(Schema schema) {
+ if (ModelUtils.isMapSchema(schema)) {
+ Schema additionalProperties = ModelUtils.getAdditionalProperties(schema);
+ String inner = getSchemaType(additionalProperties);
+ String mapInstantion = instantiationTypes.get("map");
+ if (mapInstantion != null) {
+ return mapInstantion + "";
+ }
+ return inner;
+ } else if (ModelUtils.isArraySchema(schema)) {
+ String inner = getSchemaType(ModelUtils.getSchemaItems(schema));
+ String parentType;
+ if (ModelUtils.isSet(schema)) {
+ parentType = "set";
+ } else {
+ parentType = "array";
+ }
+ return instantiationTypes.get(parentType) + "<" + inner + ">";
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Return the example value of the parameter.
+ *
+ * @param codegenParameter Codegen parameter
+ */
+ public void setParameterExampleValue(CodegenParameter codegenParameter) {
+
+ // set the example value
+ // if not specified in x-example, generate a default value
+ // TODO need to revise how to obtain the example value
+ if (codegenParameter.vendorExtensions != null && codegenParameter.vendorExtensions.containsKey("x-example")) {
+ codegenParameter.example = Json.pretty(codegenParameter.vendorExtensions.get("x-example"));
+ } else if (Boolean.TRUE.equals(codegenParameter.isBoolean)) {
+ codegenParameter.example = "true";
+ } else if (Boolean.TRUE.equals(codegenParameter.isLong)) {
+ codegenParameter.example = "789";
+ } else if (Boolean.TRUE.equals(codegenParameter.isInteger)) {
+ codegenParameter.example = "56";
+ } else if (Boolean.TRUE.equals(codegenParameter.isFloat)) {
+ codegenParameter.example = "3.4";
+ } else if (Boolean.TRUE.equals(codegenParameter.isDouble)) {
+ codegenParameter.example = "1.2";
+ } else if (Boolean.TRUE.equals(codegenParameter.isNumber)) {
+ codegenParameter.example = "8.14";
+ } else if (Boolean.TRUE.equals(codegenParameter.isBinary)) {
+ codegenParameter.example = "BINARY_DATA_HERE";
+ } else if (Boolean.TRUE.equals(codegenParameter.isByteArray)) {
+ codegenParameter.example = "BYTE_ARRAY_DATA_HERE";
+ } else if (Boolean.TRUE.equals(codegenParameter.isFile)) {
+ codegenParameter.example = "/path/to/file.txt";
+ } else if (Boolean.TRUE.equals(codegenParameter.isDate)) {
+ codegenParameter.example = "2013-10-20";
+ } else if (Boolean.TRUE.equals(codegenParameter.isDateTime)) {
+ codegenParameter.example = "2013-10-20T19:20:30+01:00";
+ } else if (Boolean.TRUE.equals(codegenParameter.isUuid)) {
+ codegenParameter.example = "38400000-8cf0-11bd-b23e-10b96e4ef00d";
+ } else if (Boolean.TRUE.equals(codegenParameter.isUri)) {
+ codegenParameter.example = "https://openapi-generator.tech";
+ } else if (Boolean.TRUE.equals(codegenParameter.isString)) {
+ codegenParameter.example = codegenParameter.paramName + "_example";
+ } else if (Boolean.TRUE.equals(codegenParameter.isFreeFormObject)) {
+ codegenParameter.example = "Object";
+ }
+
+ }
+
+ /**
+ * Return the example value of the parameter.
+ *
+ * @param codegenParameter Codegen parameter
+ * @param parameter Parameter
+ */
+ public void setParameterExampleValue(CodegenParameter codegenParameter, Parameter parameter) {
+ if (parameter.getExample() != null) {
+ codegenParameter.example = parameter.getExample().toString();
+ return;
+ }
+
+ if (parameter.getExamples() != null && !parameter.getExamples().isEmpty()) {
+ Example example = parameter.getExamples().values().iterator().next();
+ if (example.getValue() != null) {
+ codegenParameter.example = example.getValue().toString();
+ return;
+ }
+ }
+
+ Schema schema = parameter.getSchema();
+ if (schema != null && schema.getExample() != null) {
+ codegenParameter.example = schema.getExample().toString();
+ return;
+ }
+
+ setParameterExampleValue(codegenParameter);
+ }
+
+ /**
+ * Return the examples of the parameter.
+ *
+ * @param codegenParameter Codegen parameter
+ * @param parameter Parameter
+ */
+ public void setParameterExamples(CodegenParameter codegenParameter, Parameter parameter) {
+ if (parameter.getExamples() != null && !parameter.getExamples().isEmpty()) {
+ codegenParameter.examples = parameter.getExamples();
+ }
+ }
+
+ /**
+ * Return the example value of the parameter.
+ *
+ * @param codegenParameter Codegen parameter
+ * @param requestBody Request body
+ */
+ public void setParameterExampleValue(CodegenParameter codegenParameter, RequestBody requestBody) {
+ Content content = requestBody.getContent();
+
+ if (content.size() > 1) {
+ // @see ModelUtils.getSchemaFromContent()
+ once(LOGGER).debug("Multiple MediaTypes found, using only the first one");
+ }
+
+ MediaType mediaType = content.values().iterator().next();
+ if (mediaType.getExample() != null) {
+ codegenParameter.example = mediaType.getExample().toString();
+ return;
+ }
+
+ if (mediaType.getExamples() != null && !mediaType.getExamples().isEmpty()) {
+ Example example = mediaType.getExamples().values().iterator().next();
+ if (example.getValue() != null) {
+ codegenParameter.example = example.getValue().toString();
+ return;
+ }
+ }
+
+ setParameterExampleValue(codegenParameter);
+ }
+
+ /**
+ * Sets the content type, style, and explode of the parameter based on the encoding specified
+ * in the request body.
+ *
+ * @param codegenParameter Codegen parameter
+ * @param mediaType MediaType from the request body
+ */
+ public void setParameterEncodingValues(CodegenParameter codegenParameter, MediaType mediaType) {
+ if (mediaType != null && mediaType.getEncoding() != null) {
+ Encoding encoding = mediaType.getEncoding().get(codegenParameter.baseName);
+ if (encoding != null) {
+ boolean styleGiven = true;
+ Encoding.StyleEnum style = encoding.getStyle();
+ if (style == null || style == Encoding.StyleEnum.FORM) {
+ // (Unfortunately, swagger-parser-v3 will always provide 'form'
+ // when style is not specified, so we can't detect that)
+ style = Encoding.StyleEnum.FORM;
+ styleGiven = false;
+ }
+ boolean explodeGiven = true;
+ Boolean explode = encoding.getExplode();
+ if (explode == null) {
+ explode = style == Encoding.StyleEnum.FORM; // Default to True when form, False otherwise
+ explodeGiven = false;
+ }
+
+ if (!styleGiven && !explodeGiven) {
+ // Ignore contentType if style or explode are specified.
+ codegenParameter.contentType = encoding.getContentType();
+ }
+
+ codegenParameter.style = style.toString();
+ codegenParameter.isDeepObject = Encoding.StyleEnum.DEEP_OBJECT == style;
+
+ if (codegenParameter.isContainer) {
+ codegenParameter.isExplode = explode;
+ String collectionFormat = getCollectionFormat(codegenParameter);
+ codegenParameter.collectionFormat = StringUtils.isEmpty(collectionFormat) ? "csv" : collectionFormat;
+ codegenParameter.isCollectionFormatMulti = "multi".equals(collectionFormat);
+ } else {
+ codegenParameter.isExplode = false;
+ codegenParameter.collectionFormat = null;
+ codegenParameter.isCollectionFormatMulti = false;
+ }
+ } else {
+ LOGGER.debug("encoding not specified for {}", codegenParameter.baseName);
+ }
+ }
+ }
+
+ /**
+ * Return the example value of the property
+ *
+ * This method should be overridden in the generator to meet its requirement.
+ *
+ * @param schema Property schema
+ * @return string presentation of the example value of the property
+ */
+ public String toExampleValue(Schema schema) {
+ if (schema.getExample() != null) {
+ return schema.getExample().toString();
+ }
+
+ return "null";
+ }
+
+ /**
+ * Return the default value of the property
+ *
+ * This method should be overridden in the generator to meet its requirement.
+ * Return null if you do NOT want a default value.
+ * Any non-null value will cause {{#defaultValue} check to pass.
+ *
+ * @param schema Property schema
+ * @return string presentation of the default value of the property
+ */
+ @SuppressWarnings("static-method")
+ public String toDefaultValue(Schema schema) {
+ if (schema.getDefault() != null) {
+ return schema.getDefault().toString();
+ }
+
+ return "null";
+ }
+
+ /**
+ * Return the default value of the parameter
+ *
+ * Return null if you do NOT want a default value.
+ * Any non-null value will cause {{#defaultValue} check to pass.
+ *
+ * @param schema Parameter schema
+ * @return string presentation of the default value of the parameter
+ */
+ public String toDefaultParameterValue(Schema> schema) {
+ // by default works as original method to be backward compatible
+ return toDefaultValue(schema);
+ }
+
+ /**
+ * Return the default value of the parameter
+ *
+ * Return null if you do NOT want a default value.
+ * Any non-null value will cause {{#defaultValue} check to pass.
+ *
+ * @param codegenProperty Codegen Property
+ * @param schema Parameter schema
+ * @return string presentation of the default value of the parameter
+ */
+ public String toDefaultParameterValue(CodegenProperty codegenProperty, Schema> schema) {
+ // by default works as original method to be backward compatible
+ return toDefaultParameterValue(schema);
+ }
+
+ /**
+ * Return the property initialized from a data object
+ * Useful for initialization with a plain object in Javascript
+ *
+ * @param name Name of the property object
+ * @param schema Property schema
+ * @return string presentation of the default value of the property
+ */
+ @SuppressWarnings("static-method")
+ public String toDefaultValueWithParam(String name, Schema schema) {
+ return " = data." + name + ";";
+ }
+
+ /**
+ * Return the default value of the property
+ *
+ * Return null if you do NOT want a default value.
+ * Any non-null value will cause {{#defaultValue} check to pass.
+ *
+ * @param schema Property schema
+ * @param codegenProperty Codegen property
+ * @return string presentation of the default value of the property
+ */
+ public String toDefaultValue(CodegenProperty codegenProperty, Schema schema) {
+ // use toDefaultValue(schema) if generator has not overriden this method
+ return toDefaultValue(schema);
+ }
+
+ /**
+ * returns the OpenAPI type for the property. Use getAlias to handle $ref of primitive type
+ *
+ * @param schema property schema
+ * @return string presentation of the type
+ **/
+ @SuppressWarnings("static-method")
+ public String getSchemaType(Schema schema) {
+ if (ModelUtils.isComposedSchema(schema)) { // composed schema
+ // Get the interfaces, i.e. the set of elements under 'allOf', 'anyOf' or 'oneOf'.
+ List schemas = ModelUtils.getInterfaces(schema);
+
+ List names = new ArrayList<>();
+ // Build a list of the schema types under each interface.
+ // For example, if a 'allOf' composed schema has $ref children,
+ // add the type of each child to the list of names.
+ for (Schema s : schemas) {
+ names.add(getSingleSchemaType(s));
+ }
+
+ if (schema.getAllOf() != null) {
+ return toAllOfName(names, schema);
+ } else if (schema.getAnyOf() != null) { // anyOf
+ return toAnyOfName(names, schema);
+ } else if (schema.getOneOf() != null) { // oneOf
+ return toOneOfName(names, schema);
+ }
+ }
+
+ return getSingleSchemaType(schema);
+
+ }
+
+
+ protected Schema> getSchemaAdditionalProperties(Schema schema) {
+ Schema> inner = ModelUtils.getAdditionalProperties(schema);
+ if (inner == null) {
+ LOGGER.error("`{}` (map property) does not have a proper inner type defined. Default to type:string", schema.getName());
+ inner = new StringSchema().description("TODO default missing map inner type to string");
+ schema.setAdditionalProperties(inner);
+ }
+ return inner;
+ }
+
+ /**
+ * Return the name of the 'allOf' composed schema.
+ *
+ * @param names List of names
+ * @param composedSchema composed schema
+ * @return name of the allOf schema
+ */
+ @SuppressWarnings("static-method")
+ public String toAllOfName(List names, Schema composedSchema) {
+ Map exts = composedSchema.getExtensions();
+ if (exts != null && exts.containsKey("x-all-of-name")) {
+ return (String) exts.get("x-all-of-name");
+ }
+ if (names.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.debug("allOf with multiple schemas defined. Using only the first one: {}", names.get(0));
+ return names.get(0);
+ }
+ }
+
+ /**
+ * Return the name of the anyOf schema
+ *
+ * @param names List of names
+ * @param composedSchema composed schema
+ * @return name of the anyOf schema
+ */
+ @SuppressWarnings("static-method")
+ public String toAnyOfName(List names, Schema composedSchema) {
+ return "anyOf<" + String.join(",", names) + ">";
+ }
+
+ /**
+ * Return the name of the oneOf schema.
+ *
+ * This name is used to set the value of CodegenProperty.openApiType.
+ *
+ * If the 'x-one-of-name' extension is specified in the OAS document, return that value.
+ * Otherwise, a name is constructed by creating a comma-separated list of all the names
+ * of the oneOf schemas.
+ *
+ * @param names List of names
+ * @param composedSchema composed schema
+ * @return name of the oneOf schema
+ */
+ @SuppressWarnings("static-method")
+ public String toOneOfName(List names, Schema composedSchema) {
+ Map exts = composedSchema.getExtensions();
+ if (exts != null && exts.containsKey("x-one-of-name")) {
+ return (String) exts.get("x-one-of-name");
+ }
+ return "oneOf<" + String.join(",", names) + ">";
+ }
+
+ @Override
+ public Schema unaliasSchema(Schema schema) {
+ return ModelUtils.unaliasSchema(this.openAPI, schema, schemaMapping);
+ }
+
+ /**
+ * Return a string representation of the schema type, resolving aliasing and references if necessary.
+ *
+ * @param schema input
+ * @return the string representation of the schema type.
+ */
+ protected String getSingleSchemaType(Schema schema) {
+ Schema unaliasSchema = unaliasSchema(schema);
+
+ if (ModelUtils.isRefToSchemaWithProperties(unaliasSchema.get$ref())) {
+ // ref to schema's properties, e.g. #/components/schemas/Pet/properties/category
+ Schema refSchema = ModelUtils.getReferencedSchema(openAPI, unaliasSchema);
+ if (refSchema != null) {
+ return getSingleSchemaType(refSchema);
+ }
+ }
+
+ if (StringUtils.isNotBlank(unaliasSchema.get$ref())) { // reference to another definition/schema
+ // get the schema/model name from $ref
+ String schemaName = ModelUtils.getSimpleRef(unaliasSchema.get$ref());
+ if (StringUtils.isNotEmpty(schemaName)) {
+ if (schemaMapping.containsKey(schemaName)) {
+ return schemaName;
+ }
+ return getAlias(schemaName);
+ } else {
+ LOGGER.warn("Error obtaining the datatype from ref: {}. Default to 'object'", unaliasSchema.get$ref());
+ return "object";
+ }
+ } else { // primitive type or model
+ return getAlias(getPrimitiveType(unaliasSchema));
+ }
+ }
+
+ /**
+ * Return the OAI type (e.g. integer, long, etc) corresponding to a schema.
+ * $ref is not taken into account by this method.
+ *
+ * If the schema is free-form (i.e. 'type: object' with no properties) or inline
+ * schema, the returned OAI type is 'object'.
+ *
+ * @param schema
+ * @return type
+ */
+ private String getPrimitiveType(Schema schema) {
+ if (schema == null) {
+ throw new RuntimeException("schema cannot be null in getPrimitiveType");
+ } else if (typeMapping.containsKey(ModelUtils.getType(schema) + "+" + schema.getFormat())) {
+ // allows custom type_format mapping.
+ // use {type}+{format}
+ return typeMapping.get(ModelUtils.getType(schema) + "+" + schema.getFormat());
+ } else if (ModelUtils.isNullType(schema)) {
+ // The 'null' type is allowed in OAS 3.1 and above. It is not supported by OAS 3.0.x,
+ // though this tooling supports it.
+ return "null";
+ } else if (ModelUtils.isDecimalSchema(schema)) {
+ // special handle of type: string, format: number
+ return "decimal";
+ } else if (ModelUtils.isByteArraySchema(schema)) {
+ return "ByteArray";
+ } else if (ModelUtils.isFileSchema(schema)) {
+ return "file";
+ } else if (ModelUtils.isBinarySchema(schema)) {
+ return SchemaTypeUtil.BINARY_FORMAT;
+ } else if (ModelUtils.isBooleanSchema(schema)) {
+ return SchemaTypeUtil.BOOLEAN_TYPE;
+ } else if (ModelUtils.isDateSchema(schema)) {
+ return SchemaTypeUtil.DATE_FORMAT;
+ } else if (ModelUtils.isDateTimeSchema(schema)) {
+ return "DateTime";
+ } else if (ModelUtils.isNumberSchema(schema)) {
+ if (schema.getFormat() == null) { // no format defined
+ return "number";
+ } else if (ModelUtils.isFloatSchema(schema)) {
+ return SchemaTypeUtil.FLOAT_FORMAT;
+ } else if (ModelUtils.isDoubleSchema(schema)) {
+ return SchemaTypeUtil.DOUBLE_FORMAT;
+ } else {
+ LOGGER.warn("Unknown `format` {} detected for type `number`. Defaulting to `number`", schema.getFormat());
+ return "number";
+ }
+ } else if (ModelUtils.isIntegerSchema(schema)) {
+ if (ModelUtils.isUnsignedLongSchema(schema)) {
+ return "UnsignedLong";
+ } else if (ModelUtils.isUnsignedIntegerSchema(schema)) {
+ return "UnsignedInteger";
+ } else if (ModelUtils.isLongSchema(schema)) {
+ return "long";
+ } else if (ModelUtils.isShortSchema(schema)) {// int32
+ return "integer";
+ } else {
+ return ModelUtils.getType(schema); // integer
+ }
+ } else if (ModelUtils.isMapSchema(schema)) {
+ return "map";
+ } else if (ModelUtils.isArraySchema(schema)) {
+ if (ModelUtils.isSet(schema)) {
+ return "set";
+ } else {
+ return "array";
+ }
+ } else if (ModelUtils.isUUIDSchema(schema)) {
+ return "UUID";
+ } else if (ModelUtils.isURISchema(schema)) {
+ return "URI";
+ } else if (ModelUtils.isStringSchema(schema)) {
+ if (typeMapping.containsKey(schema.getFormat())) {
+ // If the format matches a typeMapping (supplied with the --typeMappings flag)
+ // then treat the format as a primitive type.
+ // This allows the typeMapping flag to add a new custom type which can then
+ // be used in the format field.
+ return schema.getFormat();
+ }
+ return "string";
+ } else if (ModelUtils.isFreeFormObject(schema)) {
+ // Note: the value of a free-form object cannot be an arbitrary type. Per OAS specification,
+ // it must be a map of string to values.
+ return "object";
+ } else if (schema.getProperties() != null && !schema.getProperties().isEmpty()) { // having property implies it's a model
+ return "object";
+ } else if (ModelUtils.isAnyType(schema)) {
+ return "AnyType";
+ } else if (StringUtils.isNotEmpty(ModelUtils.getType(schema))) {
+ if (!schemaMapping.containsKey(ModelUtils.getType(schema))) {
+ LOGGER.warn("Unknown type found in the schema: {}. To map it, please use the schema mapping option (e.g. --schema-mappings in CLI)", ModelUtils.getType(schema));
+ }
+ return ModelUtils.getType(schema);
+ }
+ // The 'type' attribute has not been set in the OAS schema, which means the value
+ // can be an arbitrary type, e.g. integer, string, object, array, number...
+ // TODO: we should return a different value to distinguish between free-form object
+ // and arbitrary type.
+ return "object";
+ }
+
+ /**
+ * Return the lowerCamelCase of the string
+ *
+ * @param name string to be lowerCamelCased
+ * @return lowerCamelCase string
+ */
+ @SuppressWarnings("static-method")
+ public String lowerCamelCase(String name) {
+ return (name.length() > 0) ? (Character.toLowerCase(name.charAt(0)) + name.substring(1)) : "";
+ }
+
+ /**
+ * Output the language-specific type declaration of a given OAS name.
+ *
+ * @param name name
+ * @return a string presentation of the type
+ */
+ @Override
+ @SuppressWarnings("static-method")
+ public String getTypeDeclaration(String name) {
+ return name;
+ }
+
+ /**
+ * Output the language-specific type declaration of the property.
+ *
+ * @param schema property schema
+ * @return a string presentation of the property type
+ */
+ @Override
+ public String getTypeDeclaration(Schema schema) {
+ if (schema == null) {
+ LOGGER.warn("Null schema found. Default type to `NULL_SCHEMA_ERR`");
+ return "NULL_SCHEMA_ERR";
+ }
+
+ String oasType = getSchemaType(schema);
+ if (typeMapping.containsKey(oasType)) {
+ return typeMapping.get(oasType);
+ }
+
+ return oasType;
+ }
+
+ /**
+ * Determine the type alias for the given type if it exists. This feature
+ * was originally developed for Java because the language does not have an aliasing
+ * mechanism of its own but later extends to handle other languages
+ *
+ * @param name The type name.
+ * @return The alias of the given type, if it exists. If there is no alias
+ * for this type, then returns the input type name.
+ */
+ public String getAlias(String name) {
+ if (typeAliases != null && typeAliases.containsKey(name)) {
+ return typeAliases.get(name);
+ }
+ return name;
+ }
+
+ /**
+ * Output the Getter name for boolean property, e.g. getActive
+ *
+ * @param name the name of the property
+ * @return getter name based on naming convention
+ */
+ @Override
+ public String toBooleanGetter(String name) {
+ return "get" + getterAndSetterCapitalize(name);
+ }
+
+ /**
+ * Output the Getter name, e.g. getSize
+ *
+ * @param name the name of the property
+ * @return getter name based on naming convention
+ */
+ @Override
+ public String toGetter(String name) {
+ return "get" + getterAndSetterCapitalize(name);
+ }
+
+ /**
+ * Output the Setter name, e.g. setSize
+ *
+ * @param name the name of the property
+ * @return setter name based on naming convention
+ */
+ @Override
+ public String toSetter(String name) {
+ return "set" + getterAndSetterCapitalize(name);
+ }
+
+ /**
+ * Output the API (class) name (capitalized) ending with the specified or default suffix
+ * Return DefaultApi if name is empty
+ *
+ * @param name the name of the Api
+ * @return capitalized Api name
+ */
+ @Override
+ public String toApiName(String name) {
+ if (name.length() == 0) {
+ return "DefaultApi";
+ }
+ return camelize(apiNamePrefix + "_" + name + "_" + apiNameSuffix);
+ }
+
+ /**
+ * Converts the OpenAPI schema name to a model name suitable for the current code generator.
+ * May be overridden for each programming language.
+ * In case the name belongs to the TypeSystem it won't be renamed.
+ *
+ * @param name the name of the model
+ * @return capitalized model name
+ */
+ @Override
+ public String toModelName(final String name) {
+ // obtain the name from modelNameMapping directly if provided
+ if (modelNameMapping.containsKey(name)) {
+ return modelNameMapping.get(name);
+ }
+
+ if (schemaKeyToModelNameCache.containsKey(name)) {
+ return schemaKeyToModelNameCache.get(name);
+ }
+
+ String camelizedName = camelize(modelNamePrefix + "_" + name + "_" + modelNameSuffix);
+ schemaKeyToModelNameCache.put(name, camelizedName);
+ return camelizedName;
+ }
+
+ private static class NamedSchema {
+ private NamedSchema(String name, Schema s, boolean required, boolean schemaIsFromAdditionalProperties) {
+ this.name = name;
+ this.schema = s;
+ this.required = required;
+ this.schemaIsFromAdditionalProperties = schemaIsFromAdditionalProperties;
+ }
+
+ private String name;
+ private Schema schema;
+ private boolean required;
+ private boolean schemaIsFromAdditionalProperties;
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ NamedSchema that = (NamedSchema) o;
+ return Objects.equals(required, that.required) &&
+ Objects.equals(name, that.name) &&
+ Objects.equals(schema, that.schema) &&
+ Objects.equals(schemaIsFromAdditionalProperties, that.schemaIsFromAdditionalProperties);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, schema, required, schemaIsFromAdditionalProperties);
+ }
+ }
+
+ Map schemaCodegenPropertyCache = new HashMap<>();
+
+ protected void updateModelForComposedSchema(CodegenModel m, Schema schema, Map allDefinitions) {
+ final Schema composed = schema;
+ Map properties = new LinkedHashMap<>();
+ List required = new ArrayList<>();
+ Map allProperties = new LinkedHashMap<>();
+ List allRequired = new ArrayList<>();
+
+ // if schema has properties outside of allOf/oneOf/anyOf also add them to m
+ if (composed.getProperties() != null && !composed.getProperties().isEmpty()) {
+ if (composed.getOneOf() != null && !composed.getOneOf().isEmpty()) {
+ LOGGER.warn("'oneOf' is intended to include only the additional optional OAS extension discriminator object. " +
+ "For more details, see https://json-schema.org/draft/2019-09/json-schema-core.html#rfc.section.9.2.1.3 and the OAS section on 'Composition and Inheritance'.");
+ }
+ addVars(m, unaliasPropertySchema(composed.getProperties()), composed.getRequired(), null, null);
+ }
+
+ // parent model
+ final String parentName = ModelUtils.getParentName(composed, allDefinitions);
+ final List allParents = ModelUtils.getAllParentsName(composed, allDefinitions, false);
+ final Schema parent = StringUtils.isBlank(parentName) || allDefinitions == null ? null : allDefinitions.get(parentName);
+
+ // TODO revise the logic below to set discriminator, xml attributes
+ if (supportsInheritance || supportsMixins) {
+ m.allVars = new ArrayList<>();
+ if (composed.getAllOf() != null) {
+ int modelImplCnt = 0; // only one inline object allowed in a ComposedModel
+ int modelDiscriminators = 0; // only one discriminator allowed in a ComposedModel
+ for (Object innerSchema : composed.getAllOf()) { // TODO need to work with anyOf, oneOf as well
+ if (m.discriminator == null && ((Schema) innerSchema).getDiscriminator() != null) {
+ LOGGER.debug("discriminator is set to null (not correctly set earlier): {}", m.name);
+ m.setDiscriminator(createDiscriminator(m.name, (Schema) innerSchema));
+ modelDiscriminators++;
+ }
+
+ if (((Schema) innerSchema).getXml() != null) {
+ m.xmlPrefix = ((Schema) innerSchema).getXml().getPrefix();
+ m.xmlNamespace = ((Schema) innerSchema).getXml().getNamespace();
+ m.xmlName = ((Schema) innerSchema).getXml().getName();
+ }
+ if (modelDiscriminators > 1) {
+ LOGGER.error("Allof composed schema is inheriting >1 discriminator. Only use one discriminator: {}", composed);
+ }
+
+ if (modelImplCnt++ > 1) {
+ LOGGER.warn("More than one inline schema specified in allOf:. Only the first one is recognized. All others are ignored.");
+ break; // only one schema with discriminator allowed in allOf
+ }
+ }
+ }
+ }
+
+ // interfaces (schemas defined in allOf, anyOf, oneOf)
+ List interfaces = ModelUtils.getInterfaces(composed);
+ if (!interfaces.isEmpty()) {
+ // m.interfaces is for backward compatibility
+ if (m.interfaces == null)
+ m.interfaces = new ArrayList<>();
+
+ for (Schema interfaceSchema : interfaces) {
+ interfaceSchema = unaliasSchema(interfaceSchema);
+
+ if (StringUtils.isBlank(interfaceSchema.get$ref())) {
+ // primitive type
+ String languageType = getTypeDeclaration(interfaceSchema);
+ CodegenProperty interfaceProperty = fromProperty(languageType, interfaceSchema, false);
+ if (ModelUtils.isArraySchema(interfaceSchema) || ModelUtils.isMapSchema(interfaceSchema)) {
+ while (interfaceProperty != null) {
+ addImport(m, interfaceProperty.complexType);
+ interfaceProperty = interfaceProperty.items;
+ }
+ }
+
+ if (composed.getAnyOf() != null) {
+ if (m.anyOf.contains(languageType)) {
+ LOGGER.debug("{} (anyOf schema) already has `{}` defined and therefore it's skipped.", m.name, languageType);
+ } else {
+ m.anyOf.add(languageType);
+
+ }
+ } else if (composed.getOneOf() != null) {
+ if (m.oneOf.contains(languageType)) {
+ LOGGER.debug("{} (oneOf schema) already has `{}` defined and therefore it's skipped.", m.name, languageType);
+ } else {
+ m.oneOf.add(languageType);
+ }
+ } else if (composed.getAllOf() != null) {
+ // no need to add primitive type to allOf, which should comprise of schemas (models) only
+ } else {
+ LOGGER.error("Composed schema has incorrect anyOf, allOf, oneOf defined: {}", composed);
+ }
+ continue;
+ }
+
+ // the rest of the section is for model
+ Schema refSchema = null;
+ String ref = ModelUtils.getSimpleRef(interfaceSchema.get$ref());
+ if (allDefinitions != null) {
+ refSchema = allDefinitions.get(ref);
+ }
+ final String modelName = toModelName(ref);
+ CodegenProperty interfaceProperty = fromProperty(modelName, interfaceSchema, false);
+ m.interfaces.add(modelName);
+ addImport(composed, refSchema, m, modelName);
+
+ if (allDefinitions != null && refSchema != null) {
+ if (allParents.contains(ref) && supportsMultipleInheritance) {
+ // multiple inheritance
+ addProperties(allProperties, allRequired, refSchema, new HashSet<>());
+ } else if (parentName != null && parentName.equals(ref) && supportsInheritance) {
+ // single inheritance
+ addProperties(allProperties, allRequired, refSchema, new HashSet<>());
+ } else {
+ // composition
+ Map newProperties = new LinkedHashMap<>();
+ addProperties(newProperties, required, refSchema, new HashSet<>());
+ mergeProperties(properties, newProperties);
+ addProperties(allProperties, allRequired, refSchema, new HashSet<>());
+ }
+ }
+
+ if (composed.getAnyOf() != null) {
+ m.anyOf.add(modelName);
+ } else if (composed.getOneOf() != null) {
+ m.oneOf.add(modelName);
+ } else if (composed.getAllOf() != null) {
+ m.allOf.add(modelName);
+ } else {
+ LOGGER.error("Composed schema has incorrect anyOf, allOf, oneOf defined: {}", composed);
+ }
+ }
+ }
+
+ if (parent != null && composed.getAllOf() != null) { // set parent for allOf only
+ m.parentSchema = parentName;
+ m.parent = toModelName(parentName);
+
+ if (supportsMultipleInheritance) {
+ m.allParents = new ArrayList<>();
+ for (String pname : allParents) {
+ String pModelName = toModelName(pname);
+ m.allParents.add(pModelName);
+ addImport(m, pModelName);
+ }
+ } else { // single inheritance
+ addImport(m, m.parent);
+ }
+ }
+
+ // child schema (properties owned by the schema itself)
+ for (Schema component : interfaces) {
+ if (component.get$ref() == null) {
+ if (component != null) {
+ // component is the child schema
+ addProperties(properties, required, component, new HashSet<>());
+
+ // includes child's properties (all, required) in allProperties, allRequired
+ addProperties(allProperties, allRequired, component, new HashSet<>());
+ }
+ // in 7.0.0 release, we comment out below to allow more than 1 child schemas in allOf
+ //break; // at most one child only
+ }
+ }
+
+ if (composed.getRequired() != null) {
+ required.addAll(composed.getRequired());
+ allRequired.addAll(composed.getRequired());
+ }
+
+ addVars(m, unaliasPropertySchema(properties), required, unaliasPropertySchema(allProperties), allRequired);
+
+ // Per OAS specification, composed schemas may use the 'additionalProperties' keyword.
+ if (supportsAdditionalPropertiesWithComposedSchema) {
+ // Process the schema specified with the 'additionalProperties' keyword.
+ // This will set the 'CodegenModel.additionalPropertiesType' field
+ // and potentially 'Codegen.parent'.
+ //
+ // Note: it's not a good idea to use single class inheritance to implement
+ // the 'additionalProperties' keyword. Code generators that use single class
+ // inheritance sometimes use the 'Codegen.parent' field to implement the
+ // 'additionalProperties' keyword. However, that would be in conflict with
+ // 'allOf' composed schemas, because these code generators also want to set
+ // 'Codegen.parent' to the first child schema of the 'allOf' schema.
+ addAdditionPropertiesToCodeGenModel(m, schema);
+ }
+
+ if (Boolean.TRUE.equals(schema.getNullable())) {
+ m.isNullable = Boolean.TRUE;
+ }
+
+ // end of code block for composed schema
+ }
+
+ /**
+ * Combines all previously-detected type entries for a schema with newly-discovered ones, to ensure
+ * that schema for items like enum include all possible values.
+ */
+ private void mergeProperties(Map existingProperties, Map newProperties) {
+ // https://github.com/OpenAPITools/openapi-generator/issues/12545
+ if (null != existingProperties && null != newProperties) {
+ Schema existingType = existingProperties.get("type");
+ Schema newType = newProperties.get("type");
+ newProperties.forEach((key, value) ->
+ existingProperties.put(key, ModelUtils.cloneSchema(value, specVersionGreaterThanOrEqualTo310(openAPI)))
+ );
+ if (null != existingType && null != newType && null != newType.getEnum() && !newType.getEnum().isEmpty()) {
+ for (Object e : newType.getEnum()) {
+ // ensure all interface enum types are added to schema
+ if (null != existingType.getEnum() && !existingType.getEnum().contains(e)) {
+ existingType.addEnumItemObject(e);
+ }
+ }
+ existingProperties.put("type", existingType);
+ }
+ }
+ }
+
+ protected void updateModelForObject(CodegenModel m, Schema schema) {
+ if (schema.getProperties() != null || schema.getRequired() != null && !(ModelUtils.isComposedSchema(schema))) {
+ // passing null to allProperties and allRequired as there's no parent
+ addVars(m, unaliasPropertySchema(schema.getProperties()), schema.getRequired(), null, null);
+ }
+ if (ModelUtils.isMapSchema(schema)) {
+ // an object or anyType composed schema that has additionalProperties set
+ addAdditionPropertiesToCodeGenModel(m, schema);
+ } else if (ModelUtils.isFreeFormObject(schema)) {
+ // non-composed object type with no properties + additionalProperties
+ // additionalProperties must be null, ObjectSchema, or empty Schema
+ addAdditionPropertiesToCodeGenModel(m, schema);
+ }
+ // process 'additionalProperties'
+ setAddProps(schema, m);
+ addRequiredVarsMap(schema, m);
+ }
+
+ protected void updateModelForAnyType(CodegenModel m, Schema schema) {
+ // The 'null' value is allowed when the OAS schema is 'any type'.
+ // See https://github.com/OAI/OpenAPI-Specification/issues/1389
+ if (Boolean.FALSE.equals(schema.getNullable())) {
+ LOGGER.error("Schema '{}' is any type, which includes the 'null' value. 'nullable' cannot be set to 'false'", m.name);
+ }
+ // m.isNullable = true;
+ if (ModelUtils.isMapSchema(schema)) {
+ // an object or anyType composed schema that has additionalProperties set
+ addAdditionPropertiesToCodeGenModel(m, schema);
+ m.isMap = true;
+ }
+ if (schema.getProperties() != null || schema.getRequired() != null && !(ModelUtils.isComposedSchema(schema))) {
+ // passing null to allProperties and allRequired as there's no parent
+ addVars(m, unaliasPropertySchema(schema.getProperties()), schema.getRequired(), null, null);
+ }
+ // process 'additionalProperties'
+ setAddProps(schema, m);
+ addRequiredVarsMap(schema, m);
+ }
+
+ protected String toTestCaseName(String specTestCaseName) {
+ return specTestCaseName;
+ }
+
+ /**
+ * A method that allows generators to pre-process test example payloads
+ * This can be useful if one needs to change how values like null in string are represented
+ *
+ * @param data the test data payload
+ * @return the updated test data payload
+ */
+ protected Object processTestExampleData(Object data) {
+ return data;
+ }
+
+ /**
+ * Processes any test cases if they exist in the components.x-test-examples vendor extensions
+ * If they exist then cast them to java class instances and return them back in a map
+ *
+ * @param refToTestCases the component schema name that the test cases are for
+ */
+ private HashMap extractSchemaTestCases(String refToTestCases) {
+ // schemaName to a map of test case name to test case
+ HashMap vendorExtensions = (HashMap) openAPI.getComponents().getExtensions();
+ if (vendorExtensions == null || !vendorExtensions.containsKey(xSchemaTestExamplesKey)) {
+ return null;
+ }
+ if (!refToTestCases.startsWith(xSchemaTestExamplesRefPrefix)) {
+ return null;
+ }
+ String schemaName = refToTestCases.substring(xSchemaTestExamplesRefPrefix.length());
+ HashMap schemaTestCases = new HashMap<>();
+ LinkedHashMap schemaNameToTestCases = (LinkedHashMap) vendorExtensions.get(xSchemaTestExamplesKey);
+
+ if (!schemaNameToTestCases.containsKey(schemaName)) {
+ return null;
+ }
+ LinkedHashMap> testNameToTesCase = (LinkedHashMap>) schemaNameToTestCases.get(schemaName);
+ for (Entry> entry : testNameToTesCase.entrySet()) {
+ LinkedHashMap testExample = (LinkedHashMap) entry.getValue();
+ String nameInSnakeCase = toTestCaseName(entry.getKey());
+ Object data = processTestExampleData(testExample.get("data"));
+ SchemaTestCase testCase = new SchemaTestCase(
+ (String) testExample.getOrDefault("description", ""),
+ new ObjectWithTypeBooleans(data),
+ (boolean) testExample.get("valid")
+ );
+ schemaTestCases.put(nameInSnakeCase, testCase);
+ }
+ return schemaTestCases;
+ }
+
+ /**
+ * Sets the booleans that define the model's type
+ *
+ * @param model the model to update
+ * @param schema the model's schema
+ */
+ protected void updateModelForString(CodegenModel model, Schema schema) {
+ // NOTE: String schemas as CodegenModel is a rare use case and may be removed at a later date.
+ if (ModelUtils.isDateTimeSchema(schema)) {
+ // NOTE: DateTime schemas as CodegenModel is a rare use case and may be removed at a later date.
+ model.setIsString(false); // for backward compatibility with 2.x
+ model.isDateTime = Boolean.TRUE;
+ } else if (ModelUtils.isDateSchema(schema)) {
+ // NOTE: Date schemas as CodegenModel is a rare use case and may be removed at a later date.
+ model.setIsString(false); // for backward compatibility with 2.x
+ model.isDate = Boolean.TRUE;
+ } else if (ModelUtils.isUUIDSchema(schema)) {
+ // NOTE: UUID schemas as CodegenModel is a rare use case and may be removed at a later date.
+ model.setIsString(false);
+ model.setIsUuid(true);
+ } else if (ModelUtils.isURISchema(schema)) {
+ model.setIsString(false);
+ model.setIsUri(true);
+ }
+ }
+
+ protected void updateModelForNumber(CodegenModel model, Schema schema) {
+ // NOTE: Number schemas as CodegenModel is a rare use case and may be removed at a later date.
+ model.isNumeric = Boolean.TRUE;
+ if (ModelUtils.isFloatSchema(schema)) { // float
+ model.isFloat = Boolean.TRUE;
+ } else if (ModelUtils.isDoubleSchema(schema)) { // double
+ model.isDouble = Boolean.TRUE;
+ }
+ }
+
+ protected void updateModelForInteger(CodegenModel model, Schema schema) {
+ // NOTE: Integral schemas as CodegenModel is a rare use case and may be removed at a later date.
+ model.isNumeric = Boolean.TRUE;
+ if (ModelUtils.isLongSchema(schema)) { // int64/long format
+ model.isLong = Boolean.TRUE;
+ } else {
+ model.isInteger = Boolean.TRUE; // older use case, int32 and unbounded int
+ if (ModelUtils.isShortSchema(schema)) { // int32
+ model.setIsShort(Boolean.TRUE);
+ }
+ }
+ }
+
+ /**
+ * Convert OAS Model object to Codegen Model object.
+ *
+ * @param name the name of the model
+ * @param schema OAS Model object
+ * @return Codegen Model object
+ */
+ @Override
+ public CodegenModel fromModel(String name, Schema schema) {
+ Map allDefinitions = ModelUtils.getSchemas(this.openAPI);
+
+ CodegenModel m = CodegenModelFactory.newInstance(CodegenModelType.MODEL);
+ if (schema.equals(trueSchema)) {
+ m.setIsBooleanSchemaTrue(true);
+ } else if (schema.equals(falseSchema)) {
+ m.setIsBooleanSchemaFalse(true);
+ }
+ // unalias schema
+ schema = unaliasSchema(schema);
+ if (schema == null) {
+ LOGGER.warn("Schema {} not found", name);
+ return null;
+ }
+
+ ModelUtils.syncValidationProperties(schema, m);
+ if (openAPI != null) {
+ HashMap schemaTestCases = extractSchemaTestCases(xSchemaTestExamplesRefPrefix + name);
+ m.testCases = schemaTestCases;
+ }
+
+ if (reservedWords.contains(name)) {
+ m.name = escapeReservedWord(name);
+ } else {
+ m.name = name;
+ }
+ m.schemaName = name; // original schema name
+ m.title = escapeText(schema.getTitle());
+ m.description = escapeText(schema.getDescription());
+ m.unescapedDescription = schema.getDescription();
+ m.classname = toModelName(name);
+ m.classVarName = toVarName(name);
+ m.classFilename = toModelFilename(name);
+ m.modelJson = Json.pretty(schema);
+ m.externalDocumentation = schema.getExternalDocs();
+ if (schema.getExtensions() != null && !schema.getExtensions().isEmpty()) {
+ m.getVendorExtensions().putAll(schema.getExtensions());
+ }
+ m.isAlias = (typeAliases.containsKey(name)
+ || isAliasOfSimpleTypes(schema)); // check if the unaliased schema is an alias of simple OAS types
+ m.setDiscriminator(createDiscriminator(name, schema));
+
+ if (schema.getDeprecated() != null) {
+ m.isDeprecated = schema.getDeprecated();
+ }
+
+ if (schema.getXml() != null) {
+ m.xmlPrefix = schema.getXml().getPrefix();
+ m.xmlNamespace = schema.getXml().getNamespace();
+ m.xmlName = schema.getXml().getName();
+ }
+ if (!ModelUtils.isAnyType(schema) && !ModelUtils.isTypeObjectSchema(schema) && !ModelUtils.isArraySchema(schema) && schema.get$ref() == null && schema.getEnum() != null && !schema.getEnum().isEmpty()) {
+ // TODO remove the anyType check here in the future ANyType models can have enums defined
+ m.isEnum = true;
+ // comment out below as allowableValues is not set in post processing model enum
+ m.allowableValues = new HashMap<>();
+ m.allowableValues.put("values", schema.getEnum());
+ }
+ if (!ModelUtils.isArraySchema(schema)) {
+ m.dataType = getSchemaType(schema);
+ }
+ if (!ModelUtils.isAnyType(schema) && Boolean.TRUE.equals(schema.getNullable())) {
+ m.isNullable = Boolean.TRUE;
+ }
+
+ m.setTypeProperties(schema);
+ m.setFormat(schema.getFormat());
+ m.setComposedSchemas(getComposedSchemas(schema));
+ if (ModelUtils.isArraySchema(schema)) {
+ CodegenProperty arrayProperty = fromProperty(name, schema, false);
+ m.setItems(arrayProperty.items);
+ m.arrayModelType = arrayProperty.complexType;
+ addParentContainer(m, name, schema);
+ } else if (ModelUtils.isIntegerSchema(schema)) { // integer type
+ updateModelForInteger(m, schema);
+ } else if (ModelUtils.isStringSchema(schema)) {
+ updateModelForString(m, schema);
+ } else if (ModelUtils.isNumberSchema(schema)) {
+ updateModelForNumber(m, schema);
+ } else if (ModelUtils.isAnyType(schema)) {
+ updateModelForAnyType(m, schema);
+ } else if (ModelUtils.isTypeObjectSchema(schema)) {
+ updateModelForObject(m, schema);
+ } else if (!ModelUtils.isNullType(schema)) {
+ // referenced models here, component that refs another component which is a model
+ // if a component references a schema which is not a generated model, the refed schema will be loaded into
+ // schema by unaliasSchema and one of the above code paths will be taken
+ }
+ if (schema.get$ref() != null) {
+ m.setRef(schema.get$ref());
+ }
+
+ if (ModelUtils.isComposedSchema(schema)) {
+ updateModelForComposedSchema(m, schema, allDefinitions);
+ }
+
+ // remove duplicated properties
+ m.removeAllDuplicatedProperty();
+
+ // set isDiscriminator on the discriminator property
+ if (m.discriminator != null) {
+ String discPropName = m.discriminator.getPropertyBaseName();
+ List> listOLists = new ArrayList<>();
+ listOLists.add(m.requiredVars);
+ listOLists.add(m.vars);
+ listOLists.add(m.allVars);
+ for (List theseVars : listOLists) {
+ for (CodegenProperty requiredVar : theseVars) {
+ if (discPropName.equals(requiredVar.baseName)) {
+ requiredVar.isDiscriminator = true;
+ }
+ }
+ }
+ }
+
+ if (m.requiredVars != null && m.requiredVars.size() > 0) {
+ m.setHasRequired(true);
+ }
+
+ if (sortModelPropertiesByRequiredFlag) {
+ SortModelPropertiesByRequiredFlag(m);
+ }
+
+ // post process model properties
+ if (m.vars != null) {
+ for (CodegenProperty prop : m.vars) {
+ postProcessModelProperty(m, prop);
+ }
+ m.hasVars = m.vars.size() > 0;
+ }
+ if (m.allVars != null) {
+ for (CodegenProperty prop : m.allVars) {
+ postProcessModelProperty(m, prop);
+ }
+ }
+
+ return m;
+ }
+
+ protected void SortModelPropertiesByRequiredFlag(CodegenModel model) {
+ Comparator comparator = new Comparator() {
+ @Override
+ public int compare(CodegenProperty one, CodegenProperty another) {
+ if (one.required == another.required) return 0;
+ else if (one.required) return -1;
+ else return 1;
+ }
+ };
+ Collections.sort(model.vars, comparator);
+ Collections.sort(model.allVars, comparator);
+ }
+
+ protected void setAddProps(Schema schema, IJsonSchemaValidationProperties property) {
+ if (schema.equals(new Schema())) {
+ // if we are trying to set additionalProperties on an empty schema stop recursing
+ return;
+ }
+ // Note: This flag is set to true if additioanl properties
+ // is set (any type, free form object, boolean true, string, etc).
+ // The variable name may be renamed later to avoid confusion.
+ boolean additionalPropertiesIsAnyType = false;
+ CodegenModel m = null;
+ if (property instanceof CodegenModel) {
+ m = (CodegenModel) property;
+ }
+ CodegenProperty addPropProp = null;
+ boolean isAdditionalPropertiesTrue = false;
+ if (schema.getAdditionalProperties() == null) {
+ if (!disallowAdditionalPropertiesIfNotPresent) {
+ isAdditionalPropertiesTrue = true;
+ addPropProp = fromProperty(getAdditionalPropertiesName(), new Schema(), false);
+ additionalPropertiesIsAnyType = true;
+ }
+ } else if (schema.getAdditionalProperties() instanceof Boolean) {
+ if (Boolean.TRUE.equals(schema.getAdditionalProperties())) {
+ isAdditionalPropertiesTrue = true;
+ addPropProp = fromProperty(getAdditionalPropertiesName(), new Schema(), false);
+ additionalPropertiesIsAnyType = true;
+ }
+ } else {
+ // if additioanl properties is set (e.g. free form object, any type, string, etc)
+ addPropProp = fromProperty(getAdditionalPropertiesName(), (Schema) schema.getAdditionalProperties(), false);
+ additionalPropertiesIsAnyType = true;
+ }
+ if (additionalPropertiesIsAnyType) {
+ property.setAdditionalPropertiesIsAnyType(true);
+ }
+ if (m != null && (isAdditionalPropertiesTrue || additionalPropertiesIsAnyType)) {
+ m.isAdditionalPropertiesTrue = true;
+ }
+ if (ModelUtils.isComposedSchema(schema) && !supportsAdditionalPropertiesWithComposedSchema) {
+ return;
+ }
+ if (addPropProp != null) {
+ property.setAdditionalProperties(addPropProp);
+ }
+ }
+
+ /**
+ * Recursively look in Schema sc for the discriminator discPropName
+ * and return a CodegenProperty with the dataType and required params set
+ * the returned CodegenProperty may not be required and it may not be of type string
+ *
+ * @param composedSchemaName The name of the sc Schema
+ * @param sc The Schema that may contain the discriminator
+ * @param discPropName The String that is the discriminator propertyName in the schema
+ * @param visitedSchemas A set of visited schema names
+ */
+ private CodegenProperty discriminatorFound(String composedSchemaName, Schema sc, String discPropName, Set visitedSchemas) {
+ if (visitedSchemas.contains(composedSchemaName)) { // recursive schema definition found
+ return null;
+ } else {
+ visitedSchemas.add(composedSchemaName);
+ }
+
+ Schema refSchema = ModelUtils.getReferencedSchema(openAPI, sc);
+ if (refSchema.getProperties() != null && refSchema.getProperties().get(discPropName) != null) {
+ Schema discSchema = (Schema) refSchema.getProperties().get(discPropName);
+ CodegenProperty cp = new CodegenProperty();
+ if (ModelUtils.isStringSchema(discSchema)) {
+ cp.isString = true;
+ }
+ cp.setRequired(false);
+ if (refSchema.getRequired() != null && refSchema.getRequired().contains(discPropName)) {
+ cp.setRequired(true);
+ }
+ return cp;
+ }
+ if (ModelUtils.isComposedSchema(refSchema)) {
+ Schema composedSchema = refSchema;
+ if (composedSchema.getAllOf() != null) {
+ // If our discriminator is in one of the allOf schemas break when we find it
+ for (Object allOf : composedSchema.getAllOf()) {
+ CodegenProperty cp = discriminatorFound(composedSchemaName, (Schema) allOf, discPropName, visitedSchemas);
+ if (cp != null) {
+ return cp;
+ }
+ }
+ }
+ if (composedSchema.getOneOf() != null && composedSchema.getOneOf().size() != 0) {
+ // All oneOf definitions must contain the discriminator
+ CodegenProperty cp = new CodegenProperty();
+ for (Object oneOf : composedSchema.getOneOf()) {
+ String modelName = ModelUtils.getSimpleRef(((Schema) oneOf).get$ref());
+ CodegenProperty thisCp = discriminatorFound(composedSchemaName, (Schema) oneOf, discPropName, visitedSchemas);
+ if (thisCp == null) {
+ once(LOGGER).warn(
+ "'{}' defines discriminator '{}', but the referenced OneOf schema '{}' is missing {}",
+ composedSchemaName, discPropName, modelName, discPropName);
+ }
+ if (cp != null && cp.dataType == null) {
+ cp = thisCp;
+ continue;
+ }
+ if (cp != thisCp) {
+ once(LOGGER).warn(
+ "'{}' defines discriminator '{}', but the OneOf schema '{}' has a different {} definition than the prior OneOf schema's. Make sure the {} type and required values are the same",
+ composedSchemaName, discPropName, modelName, discPropName, discPropName);
+ }
+ }
+ return cp;
+ }
+ if (composedSchema.getAnyOf() != null && composedSchema.getAnyOf().size() != 0) {
+ // All anyOf definitions must contain the discriminator because a min of one must be selected
+ CodegenProperty cp = new CodegenProperty();
+ for (Object anyOf : composedSchema.getAnyOf()) {
+ String modelName = ModelUtils.getSimpleRef(((Schema) anyOf).get$ref());
+ CodegenProperty thisCp = discriminatorFound(composedSchemaName, (Schema) anyOf, discPropName, visitedSchemas);
+ if (thisCp == null) {
+ once(LOGGER).warn(
+ "'{}' defines discriminator '{}', but the referenced AnyOf schema '{}' is missing {}",
+ composedSchemaName, discPropName, modelName, discPropName);
+ }
+ if (cp != null && cp.dataType == null) {
+ cp = thisCp;
+ continue;
+ }
+ if (cp != thisCp) {
+ once(LOGGER).warn(
+ "'{}' defines discriminator '{}', but the AnyOf schema '{}' has a different {} definition than the prior AnyOf schema's. Make sure the {} type and required values are the same",
+ composedSchemaName, discPropName, modelName, discPropName, discPropName);
+ }
+ }
+ return cp;
+
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Recursively look in Schema sc for the discriminator and return it
+ *
+ * @param sc The Schema that may contain the discriminator
+ * @param visitedSchemas An array list of visited schemas
+ */
+ private Discriminator recursiveGetDiscriminator(Schema sc, ArrayList visitedSchemas) {
+ Schema refSchema = ModelUtils.getReferencedSchema(openAPI, sc);
+ Discriminator foundDisc = refSchema.getDiscriminator();
+ if (foundDisc != null) {
+ return foundDisc;
+ }
+
+ if (this.getLegacyDiscriminatorBehavior()) {
+ return null;
+ }
+
+ for (Schema s : visitedSchemas) {
+ if (s == refSchema) {
+ return null;
+ }
+ }
+ visitedSchemas.add(refSchema);
+
+ Discriminator disc = new Discriminator();
+ if (ModelUtils.isComposedSchema(refSchema)) {
+ Schema composedSchema = refSchema;
+ if (composedSchema.getAllOf() != null) {
+ // If our discriminator is in one of the allOf schemas break when we find it
+ for (Object allOf : composedSchema.getAllOf()) {
+ foundDisc = recursiveGetDiscriminator((Schema) allOf, visitedSchemas);
+ if (foundDisc != null) {
+ disc.setPropertyName(foundDisc.getPropertyName());
+ disc.setMapping(foundDisc.getMapping());
+ return disc;
+ }
+ }
+ }
+ if (composedSchema.getOneOf() != null && composedSchema.getOneOf().size() != 0) {
+ // All oneOf definitions must contain the discriminator
+ Integer hasDiscriminatorCnt = 0;
+ Integer hasNullTypeCnt = 0;
+ Set discriminatorsPropNames = new HashSet<>();
+ for (Object oneOf : composedSchema.getOneOf()) {
+ if (ModelUtils.isNullType((Schema) oneOf)) {
+ // The null type does not have a discriminator. Skip.
+ hasNullTypeCnt++;
+ continue;
+ }
+ foundDisc = recursiveGetDiscriminator((Schema) oneOf, visitedSchemas);
+ if (foundDisc != null) {
+ discriminatorsPropNames.add(foundDisc.getPropertyName());
+ hasDiscriminatorCnt++;
+ }
+ }
+ if (discriminatorsPropNames.size() > 1) {
+ once(LOGGER).warn("The oneOf schemas have conflicting discriminator property names. " +
+ "oneOf schemas must have the same property name, but found " + String.join(", ", discriminatorsPropNames));
+ }
+ if (foundDisc != null && (hasDiscriminatorCnt + hasNullTypeCnt) == composedSchema.getOneOf().size() && discriminatorsPropNames.size() == 1) {
+ disc.setPropertyName(foundDisc.getPropertyName());
+ disc.setMapping(foundDisc.getMapping());
+ return disc;
+ }
+ // If the scenario when oneOf has two children and one of them is the 'null' type,
+ // there is no need for a discriminator.
+ }
+ if (composedSchema.getAnyOf() != null && composedSchema.getAnyOf().size() != 0) {
+ // All anyOf definitions must contain the discriminator because a min of one must be selected
+ Integer hasDiscriminatorCnt = 0;
+ Integer hasNullTypeCnt = 0;
+ Set discriminatorsPropNames = new HashSet<>();
+ for (Object anyOf : composedSchema.getAnyOf()) {
+ if (ModelUtils.isNullType((Schema) anyOf)) {
+ // The null type does not have a discriminator. Skip.
+ hasNullTypeCnt++;
+ continue;
+ }
+ foundDisc = recursiveGetDiscriminator((Schema) anyOf, visitedSchemas);
+ if (foundDisc != null) {
+ discriminatorsPropNames.add(foundDisc.getPropertyName());
+ hasDiscriminatorCnt++;
+ }
+ }
+ if (discriminatorsPropNames.size() > 1) {
+ once(LOGGER).warn("The anyOf schemas have conflicting discriminator property names. " +
+ "anyOf schemas must have the same property name, but found " + String.join(", ", discriminatorsPropNames));
+ }
+ if (foundDisc != null && (hasDiscriminatorCnt + hasNullTypeCnt) == composedSchema.getAnyOf().size() && discriminatorsPropNames.size() == 1) {
+ disc.setPropertyName(foundDisc.getPropertyName());
+ disc.setMapping(foundDisc.getMapping());
+ return disc;
+ }
+ // If the scenario when anyOf has two children and one of them is the 'null' type,
+ // there is no need for a discriminator.
+ }
+ }
+ return null;
+ }
+
+ /**
+ * This function is only used for composed schemas which have a discriminator
+ * Process oneOf and anyOf models in a composed schema and adds them into
+ * a list if the oneOf and anyOf models contain
+ * the required discriminator. If they don't contain the required
+ * discriminator or the discriminator is the wrong type then an error is
+ * thrown
+ *
+ * @param composedSchemaName The String model name of the composed schema where we are setting the discriminator map
+ * @param discPropName The String that is the discriminator propertyName in the schema
+ * @param c The ComposedSchema that contains the discriminator and oneOf/anyOf schemas
+ * @return the list of oneOf and anyOf MappedModel that need to be added to the discriminator map
+ */
+ protected List getOneOfAnyOfDescendants(String composedSchemaName, String discPropName, Schema c) {
+ ArrayList> listOLists = new ArrayList<>();
+ listOLists.add(c.getOneOf());
+ listOLists.add(c.getAnyOf());
+ List descendentSchemas = new ArrayList<>();
+ for (List schemaList : listOLists) {
+ if (schemaList == null) {
+ continue;
+ }
+ for (Schema sc : schemaList) {
+ if (ModelUtils.isNullType(sc)) {
+ continue;
+ }
+ String ref = sc.get$ref();
+ if (ref == null) {
+ // for schemas with no ref, it is not possible to build the discriminator map
+ // because ref is how we get the model name
+ // we only hit this use case for a schema with inline composed schemas, and one of those
+ // schemas also has inline composed schemas
+ // Note: if it is only inline one level, then the inline model resolver will move it into its own
+ // schema and make it a $ref schema in the oneOf/anyOf location
+ once(LOGGER).warn(
+ "Invalid inline schema defined in oneOf/anyOf in '{}'. Per the OpenApi spec, for this case when a composed schema defines a discriminator, the oneOf/anyOf schemas must use $ref. Change this inline definition to a $ref definition",
+ composedSchemaName);
+ }
+ CodegenProperty df = discriminatorFound(composedSchemaName, sc, discPropName, new TreeSet());
+ String modelName = ModelUtils.getSimpleRef(ref);
+ if (df == null || !df.isString || df.required != true) {
+ String msgSuffix = "";
+ if (df == null) {
+ msgSuffix += discPropName + " is missing from the schema, define it as required and type string";
+ } else {
+ if (!df.isString) {
+ msgSuffix += "invalid type for " + discPropName + ", set it to string";
+ }
+ if (df.required != true) {
+ String spacer = "";
+ if (msgSuffix.length() != 0) {
+ spacer = ". ";
+ }
+ msgSuffix += spacer + "invalid optional definition of " + discPropName + ", include it in required";
+ }
+ }
+ once(LOGGER).warn("'{}' defines discriminator '{}', but the referenced schema '{}' is incorrect. {}",
+ composedSchemaName, discPropName, modelName, msgSuffix);
+ }
+ MappedModel mm = new MappedModel(modelName, toModelName(modelName));
+ descendentSchemas.add(mm);
+ Schema cs = ModelUtils.getSchema(openAPI, modelName);
+ if (cs == null) { // cannot lookup the model based on the name
+ once(LOGGER).error("Failed to lookup the schema '{}' when processing oneOf/anyOf. Please check to ensure it's defined properly.", modelName);
+ } else {
+ Map vendorExtensions = cs.getExtensions();
+ if (vendorExtensions != null && !vendorExtensions.isEmpty() && vendorExtensions.containsKey("x-discriminator-value")) {
+ String xDiscriminatorValue = (String) vendorExtensions.get("x-discriminator-value");
+ mm = new MappedModel(xDiscriminatorValue, toModelName(modelName), true);
+ descendentSchemas.add(mm);
+ }
+ }
+ }
+ }
+ return descendentSchemas;
+ }
+
+ protected List getAllOfDescendants(String thisSchemaName) {
+ ArrayList queue = new ArrayList();
+ List descendentSchemas = new ArrayList();
+ Map schemas = ModelUtils.getSchemas(openAPI);
+ String currentSchemaName = thisSchemaName;
+ Set keys = schemas.keySet();
+
+ int count = 0;
+ // hack: avoid infinite loop on potential self-references in event our checks fail.
+ while (100000 > count++) {
+ for (String childName : keys) {
+ if (childName.equals(thisSchemaName)) {
+ continue;
+ }
+ Schema child = schemas.get(childName);
+ if (ModelUtils.isComposedSchema(child)) {
+ List parents = child.getAllOf();
+ if (parents != null) {
+ for (Schema parent : parents) {
+ String ref = parent.get$ref();
+ if (ref == null) {
+ // for schemas with no ref, it is not possible to build the discriminator map
+ // because ref is how we get the model name
+ // we hit this use case when an allOf composed schema contains an inline schema
+ continue;
+ }
+ String parentName = ModelUtils.getSimpleRef(ref);
+ if (parentName != null && parentName.equals(currentSchemaName)) {
+ if (queue.contains(childName) || descendentSchemas.stream().anyMatch(i -> childName.equals(i.getMappingName()))) {
+ throw new RuntimeException("Stack overflow hit when looking for " + thisSchemaName + " an infinite loop starting and ending at " + childName + " was seen");
+ }
+ queue.add(childName);
+ break;
+ }
+ }
+ }
+ }
+ }
+ if (queue.size() == 0) {
+ break;
+ }
+ currentSchemaName = queue.remove(0);
+ Schema cs = schemas.get(currentSchemaName);
+ Map vendorExtensions = cs.getExtensions();
+ String mappingName =
+ Optional.ofNullable(vendorExtensions)
+ .map(ve -> ve.get("x-discriminator-value"))
+ .map(discriminatorValue -> (String) discriminatorValue)
+ .orElse(currentSchemaName);
+ MappedModel mm = new MappedModel(mappingName, toModelName(currentSchemaName), !mappingName.equals(currentSchemaName));
+ descendentSchemas.add(mm);
+ }
+ return descendentSchemas;
+ }
+
+ protected CodegenDiscriminator createDiscriminator(String schemaName, Schema schema) {
+ Discriminator sourceDiscriminator = recursiveGetDiscriminator(schema, new ArrayList());
+ if (sourceDiscriminator == null) {
+ return null;
+ }
+ CodegenDiscriminator discriminator = new CodegenDiscriminator();
+ String discriminatorPropertyName = sourceDiscriminator.getPropertyName();
+ discriminator.setPropertyName(toVarName(discriminatorPropertyName));
+ discriminator.setPropertyBaseName(sourceDiscriminator.getPropertyName());
+ discriminator.setPropertyGetter(toGetter(discriminator.getPropertyName()));
+
+ if (sourceDiscriminator.getExtensions() != null) {
+ discriminator.setVendorExtensions(sourceDiscriminator.getExtensions());
+ }
+
+ // FIXME: there are other ways to define the type of the discriminator property (inline
+ // for example). Handling those scenarios is too complicated for me, I'm leaving it for
+ // the future..
+ String propertyType =
+ Optional.ofNullable(schema.getProperties())
+ .map(p -> (Schema>) p.get(discriminatorPropertyName))
+ .map(Schema::get$ref)
+ .map(ModelUtils::getSimpleRef)
+ .map(this::toModelName)
+ .orElseGet(() -> typeMapping.get("string"));
+ discriminator.setPropertyType(propertyType);
+
+ // check to see if the discriminator property is an enum string
+ if (schema.getProperties() != null &&
+ schema.getProperties().get(discriminatorPropertyName) instanceof StringSchema) {
+ StringSchema s = (StringSchema) schema.getProperties().get(discriminatorPropertyName);
+ if (s.getEnum() != null && !s.getEnum().isEmpty()) { // it's an enum string
+ discriminator.setIsEnum(true);
+ }
+ }
+
+ discriminator.setMapping(sourceDiscriminator.getMapping());
+ List uniqueDescendants = new ArrayList<>();
+ if (sourceDiscriminator.getMapping() != null && !sourceDiscriminator.getMapping().isEmpty()) {
+ for (Entry e : sourceDiscriminator.getMapping().entrySet()) {
+ String name;
+ if (e.getValue().indexOf('/') >= 0) {
+ name = ModelUtils.getSimpleRef(e.getValue());
+ if (ModelUtils.getSchema(openAPI, name) == null) {
+ once(LOGGER).error("Failed to lookup the schema '{}' when processing the discriminator mapping of oneOf/anyOf. Please check to ensure it's defined properly.", name);
+ }
+ } else {
+ name = e.getValue();
+ }
+ uniqueDescendants.add(new MappedModel(e.getKey(), toModelName(name), true));
+ }
+ }
+
+ boolean legacyUseCase = (this.getLegacyDiscriminatorBehavior() && uniqueDescendants.isEmpty());
+ if (!this.getLegacyDiscriminatorBehavior() || legacyUseCase) {
+ // for schemas that allOf inherit from this schema, add those descendants to this discriminator map
+ List otherDescendants = getAllOfDescendants(schemaName);
+ for (MappedModel otherDescendant : otherDescendants) {
+ // add only if the mapping names are not the same and the model names are not the same
+ boolean matched = false;
+ for (MappedModel uniqueDescendant : uniqueDescendants) {
+ if (uniqueDescendant.getMappingName().equals(otherDescendant.getMappingName())
+ || (uniqueDescendant.getModelName().equals(otherDescendant.getModelName()))) {
+ matched = true;
+ break;
+ }
+ }
+
+ if (matched == false) {
+ uniqueDescendants.add(otherDescendant);
+ }
+ }
+ }
+ // if there are composed oneOf/anyOf schemas, add them to this discriminator
+ if (ModelUtils.isComposedSchema(schema) && !this.getLegacyDiscriminatorBehavior()) {
+ List otherDescendants = getOneOfAnyOfDescendants(schemaName, discriminatorPropertyName, schema);
+ for (MappedModel otherDescendant : otherDescendants) {
+ if (!uniqueDescendants.contains(otherDescendant)) {
+ uniqueDescendants.add(otherDescendant);
+ }
+ }
+ }
+ if (!this.getLegacyDiscriminatorBehavior()) {
+ Collections.sort(uniqueDescendants);
+ }
+ discriminator.getMappedModels().addAll(uniqueDescendants);
+
+ return discriminator;
+ }
+
+ /**
+ * Handle the model for the 'additionalProperties' keyword in the OAS schema.
+ *
+ * @param codegenModel The codegen representation of the schema.
+ * @param schema The input OAS schema.
+ */
+ protected void addAdditionPropertiesToCodeGenModel(CodegenModel codegenModel, Schema schema) {
+ addParentContainer(codegenModel, codegenModel.name, schema);
+ }
+
+ /**
+ * Add schema's properties to "properties" and "required" list
+ *
+ * @param properties all properties
+ * @param required required property only
+ * @param schema schema in which the properties will be added to the lists
+ * @param visitedSchemas circuit-breaker - the schemas with which the method was called before for recursive structures
+ */
+ protected void addProperties(Map properties, List required, Schema schema, Set visitedSchemas) {
+ if (schema == null) {
+ return;
+ }
+
+ if (!visitedSchemas.add(schema)) {
+ return;
+ }
+ if (ModelUtils.isComposedSchema(schema)) {
+ // fix issue #16797 and #15796, constructor fail by missing parent required params
+ if (schema.getProperties() != null && !schema.getProperties().isEmpty()) {
+ properties.putAll(schema.getProperties());
+ }
+
+ if (schema.getAllOf() != null) {
+ for (Object component : schema.getAllOf()) {
+ addProperties(properties, required, (Schema) component, visitedSchemas);
+ }
+ }
+
+ if (schema.getRequired() != null) {
+ required.addAll(schema.getRequired());
+ }
+
+ if (schema.getOneOf() != null) {
+ for (Object component : schema.getOneOf()) {
+ addProperties(properties, required, (Schema) component, visitedSchemas);
+ }
+ }
+
+ if (schema.getAnyOf() != null) {
+ for (Object component : schema.getAnyOf()) {
+ addProperties(properties, required, (Schema) component, visitedSchemas);
+ }
+ }
+
+ for (String r : required) {
+ if (!properties.containsKey(r)) {
+ LOGGER.error("Required var {} not in properties", r);
+ }
+ }
+ return;
+ }
+
+ if (StringUtils.isNotBlank(schema.get$ref())) {
+ Schema interfaceSchema = ModelUtils.getReferencedSchema(this.openAPI, schema);
+ addProperties(properties, required, interfaceSchema, visitedSchemas);
+ return;
+ }
+ if (schema.getProperties() != null) {
+ properties.putAll(schema.getProperties());
+ }
+ if (schema.getRequired() != null) {
+ required.addAll(schema.getRequired());
+ }
+ }
+
+ /**
+ * Camelize the method name of the getter and setter
+ *
+ * @param name string to be camelized
+ * @return Camelized string
+ */
+ public String getterAndSetterCapitalize(String name) {
+ if (name == null || name.length() == 0) {
+ return name;
+ }
+ return camelize(toVarName(name));
+ }
+
+ protected void updatePropertyForMap(CodegenProperty property, Schema p) {
+ // throw exception if additionalProperties is false
+ if (p.getAdditionalProperties() instanceof Boolean && Boolean.FALSE.equals(p.getAdditionalProperties())) {
+ throw new RuntimeException("additionalProperties cannot be false in updatePropertyForMap.");
+ }
+ property.isContainer = true;
+ property.containerType = "map";
+ property.containerTypeMapped = typeMapping.get(property.containerType);
+ // TODO remove this hack in the future, code should use minProperties and maxProperties for object schemas
+ property.minItems = p.getMinProperties();
+ property.maxItems = p.getMaxProperties();
+
+ // handle inner property
+ Schema innerSchema = unaliasSchema(ModelUtils.getAdditionalProperties(p));
+ if (innerSchema == null) {
+ LOGGER.error("Undefined map inner type for `{}`. Default to String.", p.getName());
+ innerSchema = new StringSchema().description("//TODO automatically added by openapi-generator due to undefined type");
+ p.setAdditionalProperties(innerSchema);
+ }
+ CodegenProperty cp = fromProperty("inner", innerSchema, false);
+ updatePropertyForMap(property, cp);
+ }
+
+ protected void updatePropertyForObject(CodegenProperty property, Schema p) {
+ if (ModelUtils.isFreeFormObject(p)) {
+ // non-composed object type with no properties + additionalProperties
+ // additionalProperties must be null, ObjectSchema, or empty Schema
+ property.isFreeFormObject = true;
+ if (languageSpecificPrimitives.contains(property.dataType)) {
+ property.isPrimitiveType = true;
+ }
+ if (ModelUtils.isMapSchema(p)) {
+ // an object or anyType composed schema that has additionalProperties set
+ updatePropertyForMap(property, p);
+ } else {
+ // ObjectSchema with additionalProperties = null, can be nullable
+ property.setIsMap(false);
+ }
+ } else if (ModelUtils.isMapSchema(p)) {
+ // an object or anyType composed schema that has additionalProperties set
+ updatePropertyForMap(property, p);
+ }
+ addVarsRequiredVarsAdditionalProps(p, property);
+ }
+
+ protected void updatePropertyForAnyType(CodegenProperty property, Schema p) {
+ // The 'null' value is allowed when the OAS schema is 'any type'.
+ // See https://github.com/OAI/OpenAPI-Specification/issues/1389
+ if (Boolean.FALSE.equals(p.getNullable())) {
+ LOGGER.warn("Schema '{}' is any type, which includes the 'null' value. 'nullable' cannot be set to 'false'", p.getName());
+ }
+
+ property.isNullable = property.isNullable ||
+ !(ModelUtils.isComposedSchema(p)) ||
+ p.getAllOf() == null ||
+ p.getAllOf().size() == 0;
+ if (languageSpecificPrimitives.contains(property.dataType)) {
+ property.isPrimitiveType = true;
+ }
+ if (ModelUtils.isMapSchema(p)) {
+ // an object or anyType composed schema that has additionalProperties set
+ // some of our code assumes that any type schema with properties defined will be a map
+ // even though it should allow in any type and have map constraints for properties
+ updatePropertyForMap(property, p);
+ }
+ addVarsRequiredVarsAdditionalProps(p, property);
+ }
+
+ protected void updatePropertyForString(CodegenProperty property, Schema p) {
+ if (ModelUtils.isByteArraySchema(p)) {
+ property.setIsString(false);
+ property.isByteArray = true;
+ } else if (ModelUtils.isBinarySchema(p)) {
+ property.isBinary = true;
+ property.isFile = true; // file = binary in OAS3
+ } else if (ModelUtils.isUUIDSchema(p)) {
+ property.isUuid = true;
+ } else if (ModelUtils.isURISchema(p)) {
+ property.isUri = true;
+ } else if (ModelUtils.isEmailSchema(p)) {
+ property.isEmail = true;
+ } else if (ModelUtils.isPasswordSchema(p)) {
+ property.isPassword = true;
+ } else if (ModelUtils.isDateSchema(p)) { // date format
+ property.setIsString(false); // for backward compatibility with 2.x
+ property.isDate = true;
+ } else if (ModelUtils.isDateTimeSchema(p)) { // date-time format
+ property.setIsString(false); // for backward compatibility with 2.x
+ property.isDateTime = true;
+ } else if (ModelUtils.isDecimalSchema(p)) { // type: string, format: number
+ property.isDecimal = true;
+ property.setIsString(false);
+ }
+ property.pattern = toRegularExpression(p.getPattern());
+ }
+
+ protected void updatePropertyForNumber(CodegenProperty property, Schema p) {
+ property.isNumeric = Boolean.TRUE;
+ if (ModelUtils.isFloatSchema(p)) { // float
+ property.isFloat = Boolean.TRUE;
+ } else if (ModelUtils.isDoubleSchema(p)) { // double
+ property.isDouble = Boolean.TRUE;
+ }
+ }
+
+ protected void updatePropertyForInteger(CodegenProperty property, Schema p) {
+ property.isNumeric = Boolean.TRUE;
+ if (ModelUtils.isLongSchema(p)) { // int64/long format
+ property.isLong = Boolean.TRUE;
+ } else {
+ property.isInteger = Boolean.TRUE; // older use case, int32 and unbounded int
+ if (ModelUtils.isShortSchema(p)) { // int32
+ property.setIsShort(Boolean.TRUE);
+ }
+ }
+ }
+
+ /**
+ * TODO remove this in 7.0.0 as a breaking change
+ * This method was kept when required was added to the fromProperty signature
+ * to ensure that the change was non-breaking
+ *
+ * @param name name of the property
+ * @param p OAS property schema
+ * @param required true if the property is required in the next higher object schema, false otherwise
+ * @return Codegen Property object
+ */
+ public CodegenProperty fromProperty(String name, Schema p, boolean required) {
+ return fromProperty(name, p, required, false);
+ }
+
+
+ /**
+ * TODO remove this in 7.0.0 as a breaking change
+ * This method was kept when required was added to the fromProperty signature
+ * to ensure that the change was non-breaking
+ *
+ * @param name name of the property
+ * @param p OAS property schema
+ * @return Codegen Property object
+ */
+ public CodegenProperty fromProperty(String name, Schema p) {
+ return fromProperty(name, p, false, false);
+ }
+
+ /**
+ * Convert OAS Property object to Codegen Property object.
+ *
+ * The return value is cached. An internal cache is looked up to determine
+ * if the CodegenProperty return value has already been instantiated for
+ * the (String name, Schema p) arguments.
+ * Any subsequent processing of the CodegenModel return value must be idempotent
+ * for a given (String name, Schema schema).
+ *
+ * @param name name of the property
+ * @param p OAS property schema
+ * @param required true if the property is required in the next higher object schema, false otherwise
+ * @param schemaIsFromAdditionalProperties true if the property is a required property defined by additional properties schema
+ * If this is the actual additionalProperties schema not defining a required property, then
+ * the value should be false
+ * @return Codegen Property object
+ */
+ public CodegenProperty fromProperty(String name, Schema p, boolean required, boolean schemaIsFromAdditionalProperties) {
+ if (p == null) {
+ LOGGER.error("Undefined property/schema for `{}`. Default to type:string.", name);
+ return null;
+ }
+ LOGGER.debug("debugging fromProperty for {}: {}", name, p);
+ NamedSchema ns = new NamedSchema(name, p, required, schemaIsFromAdditionalProperties);
+ CodegenProperty cpc = schemaCodegenPropertyCache.get(ns);
+ if (cpc != null) {
+ LOGGER.debug("Cached fromProperty for {} : {} required={}", name, p.getName(), required);
+ return cpc;
+ }
+
+ // if it's ref to schema's properties, get the actual schema defined in the properties
+ Schema refToPropertiesSchema = ModelUtils.getSchemaFromRefToSchemaWithProperties(openAPI, p.get$ref());
+ if (refToPropertiesSchema != null) {
+ p = refToPropertiesSchema;
+ return fromProperty(name, refToPropertiesSchema, required, schemaIsFromAdditionalProperties);
+ }
+
+ Schema original = null;
+ // check if it's allOf (only 1 sub schema) with or without default/nullable/etc set in the top level
+ if (ModelUtils.isAllOf(p) && p.getAllOf().size() == 1) {
+ if (p.getAllOf().get(0) instanceof Schema) {
+ original = p;
+ p = (Schema) p.getAllOf().get(0);
+ } else {
+ LOGGER.error("Unknown type in allOf schema. Please report the issue via openapi-generator's Github issue tracker.");
+ }
+ } else if (p.get$ref() != null) { // it's a ref
+ original = p;
+ }
+
+ CodegenProperty property = CodegenModelFactory.newInstance(CodegenModelType.PROPERTY);
+ if (p.equals(trueSchema)) {
+ property.setIsBooleanSchemaTrue(true);
+ } else if (p.equals(falseSchema)) {
+ property.setIsBooleanSchemaFalse(true);
+ }
+
+ // unalias schema
+ p = unaliasSchema(p);
+
+ property.setSchemaIsFromAdditionalProperties(schemaIsFromAdditionalProperties);
+ property.required = required;
+ ModelUtils.syncValidationProperties(p, property);
+ property.setFormat(p.getFormat());
+
+ property.name = toVarName(name);
+ property.baseName = name;
+ if (ModelUtils.getType(p) == null) {
+ property.openApiType = getSchemaType(p);
+ } else {
+ property.openApiType = ModelUtils.getType(p);
+ }
+ property.nameInPascalCase = camelize(property.name);
+ property.nameInCamelCase = camelize(property.name, LOWERCASE_FIRST_LETTER);
+ property.nameInSnakeCase = CaseFormat.UPPER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, property.nameInPascalCase);
+ property.description = escapeText(p.getDescription());
+ property.unescapedDescription = p.getDescription();
+ property.title = p.getTitle();
+ property.getter = toGetter(name);
+ property.setter = toSetter(name);
+ // put toExampleValue in a try-catch block to log the error as example values are not critical
+ try {
+ property.example = toExampleValue(p);
+ } catch (Exception e) {
+ LOGGER.error("Error in generating `example` for the property {}. Default to ERROR_TO_EXAMPLE_VALUE. Enable debugging for more info.", property.baseName);
+ LOGGER.debug("Exception from toExampleValue: {}", e.getMessage());
+ property.example = "ERROR_TO_EXAMPLE_VALUE";
+ }
+
+ property.jsonSchema = Json.pretty(Json.mapper().convertValue(p, TreeMap.class));
+
+ if (p.getDeprecated() != null) {
+ property.deprecated = p.getDeprecated();
+ } else if (p.get$ref() != null) {
+ // Since $ref should be replaced with the model it refers
+ // to, $ref'ing a model with 'deprecated' set should cause
+ // the property to reflect the model's 'deprecated' value.
+ String ref = ModelUtils.getSimpleRef(p.get$ref());
+ if (ref != null) {
+ Schema referencedSchema = ModelUtils.getSchemas(this.openAPI).get(ref);
+ if (referencedSchema != null && referencedSchema.getDeprecated() != null) {
+ property.deprecated = referencedSchema.getDeprecated();
+ }
+ }
+ }
+ if (p.getReadOnly() != null) {
+ property.isReadOnly = p.getReadOnly();
+ }
+ if (p.getWriteOnly() != null) {
+ property.isWriteOnly = p.getWriteOnly();
+ }
+ if (p.getNullable() != null) {
+ property.isNullable = p.getNullable();
+ }
+
+ if (p.getExtensions() != null && !p.getExtensions().isEmpty()) {
+ property.getVendorExtensions().putAll(p.getExtensions());
+ } else if (p.get$ref() != null) {
+ Schema referencedSchema = ModelUtils.getReferencedSchema(this.openAPI, p);
+ if (referencedSchema.getExtensions() != null && !referencedSchema.getExtensions().isEmpty()) {
+ property.getVendorExtensions().putAll(referencedSchema.getExtensions());
+ }
+ }
+
+ //Inline enum case:
+ if (p.getEnum() != null && !p.getEnum().isEmpty()) {
+ List _enum = p.getEnum();
+ property._enum = new ArrayList<>();
+ for (Object i : _enum) {
+ property._enum.add(String.valueOf(i));
+ }
+ property.isEnum = true;
+ property.isInnerEnum = true;
+
+ Map allowableValues = new HashMap<>();
+ allowableValues.put("values", _enum);
+ if (allowableValues.size() > 0) {
+ property.allowableValues = allowableValues;
+ }
+ }
+
+ Schema referencedSchema = ModelUtils.getReferencedSchema(this.openAPI, p);
+
+ //Referenced enum case:
+ if (referencedSchema != p && referencedSchema.getEnum() != null && !referencedSchema.getEnum().isEmpty()) {
+ List _enum = referencedSchema.getEnum();
+
+ property.isEnumRef = true;
+
+ Map allowableValues = new HashMap<>();
+ allowableValues.put("values", _enum);
+ if (allowableValues.size() > 0) {
+ property.allowableValues = allowableValues;
+ }
+ }
+
+ // set isNullable using nullable or x-nullable in the schema
+ if (referencedSchema.getNullable() != null) {
+ property.isNullable = referencedSchema.getNullable();
+ } else if (referencedSchema.getExtensions() != null &&
+ referencedSchema.getExtensions().containsKey("x-nullable")) {
+ property.isNullable = (Boolean) referencedSchema.getExtensions().get("x-nullable");
+ }
+
+ final XML referencedSchemaXml = referencedSchema.getXml();
+
+ if (referencedSchemaXml != null) {
+ property.xmlName = referencedSchemaXml.getName();
+ property.xmlNamespace = referencedSchemaXml.getNamespace();
+ property.xmlPrefix = referencedSchemaXml.getPrefix();
+ if (referencedSchemaXml.getAttribute() != null) {
+ property.isXmlAttribute = referencedSchemaXml.getAttribute();
+ }
+ if (referencedSchemaXml.getWrapped() != null) {
+ property.isXmlWrapped = referencedSchemaXml.getWrapped();
+ }
+ }
+
+ if (p.getXml() != null) {
+ if (p.getXml().getAttribute() != null) {
+ property.isXmlAttribute = p.getXml().getAttribute();
+ }
+ if (p.getXml().getWrapped() != null) {
+ property.isXmlWrapped = p.getXml().getWrapped();
+ }
+ property.xmlPrefix = p.getXml().getPrefix();
+ property.xmlName = p.getXml().getName();
+ property.xmlNamespace = p.getXml().getNamespace();
+ }
+
+ property.dataType = getTypeDeclaration(p);
+ property.dataFormat = p.getFormat();
+ property.baseType = getSchemaType(p);
+
+ // this can cause issues for clients which don't support enums
+ if (property.isEnum) {
+ property.datatypeWithEnum = toEnumName(property);
+ property.enumName = toEnumName(property);
+ } else {
+ property.datatypeWithEnum = property.dataType;
+ }
+
+ property.setTypeProperties(p);
+ property.setComposedSchemas(getComposedSchemas(p));
+ if (ModelUtils.isIntegerSchema(p)) { // integer type
+ updatePropertyForInteger(property, p);
+ } else if (ModelUtils.isBooleanSchema(p)) { // boolean type
+ property.getter = toBooleanGetter(name);
+ } else if (ModelUtils.isFileSchema(p) && !ModelUtils.isStringSchema(p)) {
+ // swagger v2 only, type file
+ property.isFile = true;
+ } else if (ModelUtils.isStringSchema(p)) {
+ updatePropertyForString(property, p);
+ } else if (ModelUtils.isNumberSchema(p)) {
+ updatePropertyForNumber(property, p);
+ } else if (ModelUtils.isArraySchema(p)) {
+ // default to string if inner item is undefined
+ property.isContainer = true;
+ if (ModelUtils.isSet(p)) {
+ property.containerType = "set";
+ property.containerTypeMapped = typeMapping.get(property.containerType);
+ } else {
+ property.containerType = "array";
+ property.containerTypeMapped = typeMapping.get(property.containerType);
+ }
+ property.baseType = getSchemaType(p);
+
+ // handle inner property
+ String itemName = getItemsName(p, name);
+ Schema innerSchema = unaliasSchema(ModelUtils.getSchemaItems(p));
+ CodegenProperty cp = fromProperty(itemName, innerSchema, false);
+ updatePropertyForArray(property, cp);
+ } else if (ModelUtils.isTypeObjectSchema(p)) {
+ updatePropertyForObject(property, p);
+ } else if (ModelUtils.isAnyType(p)) {
+ updatePropertyForAnyType(property, p);
+ } else if (!ModelUtils.isNullType(p)) {
+ // referenced model
+ }
+ if (p.get$ref() != null) {
+ property.setRef(p.get$ref());
+ }
+
+ boolean isAnyTypeWithNothingElseSet = (ModelUtils.isAnyType(p) &&
+ (p.getProperties() == null || p.getProperties().isEmpty()) &&
+ !ModelUtils.isComposedSchema(p) &&
+ p.getAdditionalProperties() == null && p.getNot() == null && p.getEnum() == null);
+
+ if (!ModelUtils.isArraySchema(p) && !ModelUtils.isMapSchema(p) && !ModelUtils.isFreeFormObject(p) && !isAnyTypeWithNothingElseSet) {
+ /* schemas that are not Array, not ModelUtils.isMapSchema, not isFreeFormObject, not AnyType with nothing else set
+ * so primitive schemas int, str, number, referenced schemas, AnyType schemas with properties, enums, or composition
+ */
+ String type = getSchemaType(p);
+ setNonArrayMapProperty(property, type);
+ property.isModel = (ModelUtils.isComposedSchema(referencedSchema) || ModelUtils.isObjectSchema(referencedSchema)) && ModelUtils.isModel(referencedSchema);
+ }
+
+ // restore original schema with default value, nullable, readonly etc
+ if (original != null) {
+ p = original;
+ // evaluate common attributes if defined in the top level
+ if (p.getNullable() != null) {
+ property.isNullable = p.getNullable();
+ } else if (p.getExtensions() != null && p.getExtensions().containsKey("x-nullable")) {
+ property.isNullable = (Boolean) p.getExtensions().get("x-nullable");
+ }
+
+ if (p.getReadOnly() != null) {
+ property.isReadOnly = p.getReadOnly();
+ }
+
+ if (p.getWriteOnly() != null) {
+ property.isWriteOnly = p.getWriteOnly();
+ }
+ if (original.getExtensions() != null) {
+ property.getVendorExtensions().putAll(original.getExtensions());
+ }
+ if (original.getDeprecated() != null) {
+ property.deprecated = p.getDeprecated();
+ }
+ if (original.getDescription() != null) {
+ property.description = escapeText(p.getDescription());
+ property.unescapedDescription = p.getDescription();
+ }
+ if (original.getMaxLength() != null) {
+ property.setMaxLength(original.getMaxLength());
+ }
+ if (original.getMinLength() != null) {
+ property.setMinLength(original.getMinLength());
+ }
+ if (original.getMaxItems() != null) {
+ property.setMaxItems(original.getMaxItems());
+ }
+ if (original.getMinItems() != null) {
+ property.setMinItems(original.getMinItems());
+ }
+ if (original.getMaximum() != null) {
+ property.setMaximum(String.valueOf(original.getMaximum().doubleValue()));
+ }
+ if (original.getMinimum() != null) {
+ property.setMinimum(String.valueOf(original.getMinimum().doubleValue()));
+ }
+ if (original.getTitle() != null) {
+ property.setTitle(original.getTitle());
+ }
+ }
+
+ // set the default value
+ property.defaultValue = toDefaultValue(property, p);
+ property.defaultValueWithParam = toDefaultValueWithParam(name, p);
+
+ LOGGER.debug("debugging from property return: {}", property);
+ schemaCodegenPropertyCache.put(ns, property);
+ return property;
+ }
+
+ /**
+ * Update property for array(list) container
+ *
+ * @param property Codegen property
+ * @param innerProperty Codegen inner property of map or list
+ */
+ protected void updatePropertyForArray(CodegenProperty property, CodegenProperty innerProperty) {
+ if (innerProperty == null) {
+ if (LOGGER.isWarnEnabled()) {
+ LOGGER.warn("skipping invalid array property {}", Json.pretty(property));
+ }
+ return;
+ }
+ property.dataFormat = innerProperty.dataFormat;
+ if (!languageSpecificPrimitives.contains(innerProperty.baseType)) {
+ property.complexType = innerProperty.baseType;
+ } else {
+ property.isPrimitiveType = true;
+ }
+ property.items = innerProperty;
+ property.mostInnerItems = getMostInnerItems(innerProperty);
+ // inner item is Enum
+ if (isPropertyInnerMostEnum(property)) {
+ // isEnum is set to true when the type is an enum
+ // or the inner type of an array/map is an enum
+ property.isEnum = true;
+ property.isInnerEnum = true;
+ // update datatypeWithEnum and default value for array
+ // e.g. List => List
+ updateDataTypeWithEnumForArray(property);
+ // set allowable values to enum values (including array/map of enum)
+ property.allowableValues = getInnerEnumAllowableValues(property);
+ }
+
+ }
+
+ /**
+ * Update property for map container
+ *
+ * @param property Codegen property
+ * @param innerProperty Codegen inner property of map or list
+ */
+ protected void updatePropertyForMap(CodegenProperty property, CodegenProperty innerProperty) {
+ if (innerProperty == null) {
+ if (LOGGER.isWarnEnabled()) {
+ LOGGER.warn("skipping invalid map property {}", Json.pretty(property));
+ }
+ return;
+ }
+ if (!languageSpecificPrimitives.contains(innerProperty.baseType)) {
+ property.complexType = innerProperty.baseType;
+ } else {
+ property.isPrimitiveType = true;
+ }
+ // TODO fix this, map should not be assigning properties to items
+ property.items = innerProperty;
+ property.mostInnerItems = getMostInnerItems(innerProperty);
+ property.dataFormat = innerProperty.dataFormat;
+ // inner item is Enum
+ if (isPropertyInnerMostEnum(property)) {
+ // isEnum is set to true when the type is an enum
+ // or the inner type of an array/map is an enum
+ property.isEnum = true;
+ property.isInnerEnum = true;
+ // update datatypeWithEnum and default value for map
+ // e.g. Dictionary => Dictionary
+ updateDataTypeWithEnumForMap(property);
+ // set allowable values to enum values (including array/map of enum)
+ property.allowableValues = getInnerEnumAllowableValues(property);
+ }
+
+ }
+
+ /**
+ * Update property for map container
+ *
+ * @param property Codegen property
+ * @return True if the inner most type is enum
+ */
+ protected Boolean isPropertyInnerMostEnum(CodegenProperty property) {
+ CodegenProperty currentProperty = getMostInnerItems(property);
+
+ return currentProperty != null && currentProperty.isEnum;
+ }
+
+ protected CodegenProperty getMostInnerItems(CodegenProperty property) {
+ CodegenProperty currentProperty = property;
+ while (currentProperty != null && (Boolean.TRUE.equals(currentProperty.isMap)
+ || Boolean.TRUE.equals(currentProperty.isArray)) && currentProperty.items != null) {
+ currentProperty = currentProperty.items;
+ }
+ return currentProperty;
+ }
+
+ protected Map getInnerEnumAllowableValues(CodegenProperty property) {
+ CodegenProperty currentProperty = getMostInnerItems(property);
+
+ return currentProperty == null ? new HashMap<>() : currentProperty.allowableValues;
+ }
+
+ /**
+ * Update datatypeWithEnum for array container
+ *
+ * @param property Codegen property
+ */
+ protected void updateDataTypeWithEnumForArray(CodegenProperty property) {
+ CodegenProperty baseItem = property.items;
+ while (baseItem != null && (Boolean.TRUE.equals(baseItem.isMap)
+ || Boolean.TRUE.equals(baseItem.isArray))) {
+ baseItem = baseItem.items;
+ }
+ if (baseItem != null) {
+ // set both datatype and datetypeWithEnum as only the inner type is enum
+ property.datatypeWithEnum = property.datatypeWithEnum.replace(baseItem.baseType, toEnumName(baseItem));
+
+ // naming the enum with respect to the language enum naming convention
+ // e.g. remove [], {} from array/map of enum
+ property.enumName = toEnumName(property);
+
+ // set default value for variable with inner enum
+ if (property.defaultValue != null) {
+ property.defaultValue = property.defaultValue.replace(baseItem.baseType, toEnumName(baseItem));
+ }
+
+ updateCodegenPropertyEnum(property);
+ }
+ }
+
+ /**
+ * Update datatypeWithEnum for map container
+ *
+ * @param property Codegen property
+ */
+ protected void updateDataTypeWithEnumForMap(CodegenProperty property) {
+ CodegenProperty baseItem = property.items;
+ while (baseItem != null && (Boolean.TRUE.equals(baseItem.isMap)
+ || Boolean.TRUE.equals(baseItem.isArray))) {
+ baseItem = baseItem.items;
+ }
+
+ if (baseItem != null) {
+ // set both datatype and datetypeWithEnum as only the inner type is enum
+ property.datatypeWithEnum = property.datatypeWithEnum.replace(", " + baseItem.baseType, ", " + toEnumName(baseItem));
+
+ // naming the enum with respect to the language enum naming convention
+ // e.g. remove [], {} from array/map of enum
+ property.enumName = toEnumName(property);
+
+ // set default value for variable with inner enum
+ if (property.defaultValue != null) {
+ property.defaultValue = property.defaultValue.replace(", " + property.items.baseType, ", " + toEnumName(property.items));
+ }
+
+ updateCodegenPropertyEnum(property);
+ }
+ }
+
+ protected void setNonArrayMapProperty(CodegenProperty property, String type) {
+ property.isContainer = false;
+ if (languageSpecificPrimitives().contains(type)) {
+ property.isPrimitiveType = true;
+ } else {
+ property.complexType = property.baseType;
+ property.isModel = true;
+ }
+ }
+
+ /**
+ * Override with any special handling of response codes
+ *
+ * @param responses OAS Operation's responses
+ * @return default method response or null
if not found
+ */
+ protected ApiResponse findMethodResponse(ApiResponses responses) {
+ String code = null;
+ for (String responseCode : responses.keySet()) {
+ if (responseCode.startsWith("2") || responseCode.equals("default")) {
+ if (code == null || code.compareTo(responseCode) > 0) {
+ code = responseCode;
+ }
+ }
+ }
+ if (code == null) {
+ return null;
+ }
+ return responses.get(code);
+ }
+
+ /**
+ * Set op's returnBaseType, returnType, examples etc.
+ *
+ * @param operation endpoint Operation
+ * @param schemas a map of the schemas in the openapi spec
+ * @param op endpoint CodegenOperation
+ * @param methodResponse the default ApiResponse for the endpoint
+ */
+ protected void handleMethodResponse(Operation operation,
+ Map schemas,
+ CodegenOperation op,
+ ApiResponse methodResponse) {
+ handleMethodResponse(operation, schemas, op, methodResponse, Collections.emptyMap());
+ }
+
+ /**
+ * Set op's returnBaseType, returnType, examples etc.
+ *
+ * @param operation endpoint Operation
+ * @param schemas a map of the schemas in the openapi spec
+ * @param op endpoint CodegenOperation
+ * @param methodResponse the default ApiResponse for the endpoint
+ * @param schemaMappings mappings of external types to be omitted by unaliasing
+ */
+ protected void handleMethodResponse(Operation operation,
+ Map schemas,
+ CodegenOperation op,
+ ApiResponse methodResponse,
+ Map schemaMappings) {
+ Schema responseSchema = unaliasSchema(ModelUtils.getSchemaFromResponse(openAPI, methodResponse));
+
+ if (responseSchema != null) {
+ CodegenProperty cm = fromProperty("response", responseSchema, false);
+
+ if (ModelUtils.isArraySchema(responseSchema)) {
+ CodegenProperty innerProperty = fromProperty("response", ModelUtils.getSchemaItems(responseSchema), false);
+ op.returnBaseType = innerProperty.baseType;
+ } else if (ModelUtils.isMapSchema(responseSchema)) {
+ CodegenProperty innerProperty = fromProperty("response", ModelUtils.getAdditionalProperties(responseSchema), false);
+ op.returnBaseType = innerProperty.baseType;
+ } else {
+ if (cm.complexType != null) {
+ op.returnBaseType = cm.complexType;
+ } else {
+ op.returnBaseType = cm.baseType;
+ }
+ }
+
+ op.defaultResponse = toDefaultValue(responseSchema);
+ op.returnType = cm.dataType;
+ op.returnFormat = cm.dataFormat;
+ op.hasReference = schemas != null && schemas.containsKey(op.returnBaseType);
+
+ // lookup discriminator
+ Schema schema = null;
+ if (schemas != null) {
+ schema = schemas.get(op.returnBaseType);
+ }
+ if (schema != null) {
+ CodegenModel cmod = fromModel(op.returnBaseType, schema);
+ op.discriminator = cmod.discriminator;
+ }
+
+ if (cm.isContainer) {
+ op.returnContainer = cm.containerType;
+ if ("map".equals(cm.containerType)) {
+ op.isMap = true;
+ } else if ("list".equalsIgnoreCase(cm.containerType)) {
+ op.isArray = true;
+ } else if ("array".equalsIgnoreCase(cm.containerType)) {
+ op.isArray = true;
+ } else if ("set".equalsIgnoreCase(cm.containerType)) {
+ op.isArray = true;
+ }
+ } else {
+ op.returnSimpleType = true;
+ }
+ if (languageSpecificPrimitives().contains(op.returnBaseType) || op.returnBaseType == null) {
+ op.returnTypeIsPrimitive = true;
+ }
+ op.returnProperty = cm;
+ }
+ addHeaders(methodResponse, op.responseHeaders);
+ }
+
+ /**
+ * Convert OAS Operation object to Codegen Operation object
+ *
+ * @param httpMethod HTTP method
+ * @param operation OAS operation object
+ * @param path the path of the operation
+ * @param servers list of servers
+ * @return Codegen Operation object
+ */
+ @Override
+ public CodegenOperation fromOperation(String path,
+ String httpMethod,
+ Operation operation,
+ List servers) {
+ LOGGER.debug("fromOperation => operation: {}", operation);
+ if (operation == null)
+ throw new RuntimeException("operation cannot be null in fromOperation");
+
+ Map schemas = ModelUtils.getSchemas(this.openAPI);
+ CodegenOperation op = CodegenModelFactory.newInstance(CodegenModelType.OPERATION);
+ Set imports = new HashSet<>();
+ if (operation.getExtensions() != null && !operation.getExtensions().isEmpty()) {
+ op.vendorExtensions.putAll(operation.getExtensions());
+
+ Object isCallbackRequest = op.vendorExtensions.remove("x-callback-request");
+ op.isCallbackRequest = Boolean.TRUE.equals(isCallbackRequest);
+ }
+
+ // servers setting
+ if (operation.getServers() != null && !operation.getServers().isEmpty()) {
+ // use operation-level servers first if defined
+ op.servers = fromServers(operation.getServers());
+ } else if (servers != null && !servers.isEmpty()) {
+ // use path-level servers
+ op.servers = fromServers(servers);
+ }
+
+ // store the original operationId for plug-in
+ op.operationIdOriginal = operation.getOperationId();
+ op.operationId = getOrGenerateOperationId(operation, path, httpMethod);
+
+ if (isStrictSpecBehavior() && !path.startsWith("/")) {
+ // modifies an operation.path to strictly conform to OpenAPI Spec
+ op.path = "/" + path;
+ } else {
+ op.path = path;
+ }
+
+ op.summary = escapeText(operation.getSummary());
+ op.unescapedNotes = operation.getDescription();
+ op.notes = escapeText(operation.getDescription());
+ op.hasConsumes = false;
+ op.hasProduces = false;
+ if (operation.getDeprecated() != null) {
+ op.isDeprecated = operation.getDeprecated();
+ }
+
+ addConsumesInfo(operation, op);
+
+ if (operation.getResponses() != null && !operation.getResponses().isEmpty()) {
+ ApiResponse methodResponse = findMethodResponse(operation.getResponses());
+ for (Entry operationGetResponsesEntry : operation.getResponses().entrySet()) {
+ String key = operationGetResponsesEntry.getKey();
+ ApiResponse response = operationGetResponsesEntry.getValue();
+ addProducesInfo(response, op);
+ CodegenResponse r = fromResponse(key, response);
+ Map headers = response.getHeaders();
+ if (headers != null) {
+ List responseHeaders = new ArrayList<>();
+ for (Entry entry : headers.entrySet()) {
+ String headerName = entry.getKey();
+ Header header = ModelUtils.getReferencedHeader(this.openAPI, entry.getValue());
+ CodegenParameter responseHeader = headerToCodegenParameter(header, headerName, imports, String.format(Locale.ROOT, "%sResponseParameter", r.code));
+ responseHeaders.add(responseHeader);
+ }
+ r.setResponseHeaders(responseHeaders);
+ }
+ String mediaTypeSchemaSuffix = String.format(Locale.ROOT, "%sResponseBody", r.code);
+ r.setContent(getContent(response.getContent(), imports, mediaTypeSchemaSuffix));
+
+ if (r.baseType != null &&
+ !defaultIncludes.contains(r.baseType) &&
+ !languageSpecificPrimitives.contains(r.baseType)) {
+ imports.add(r.baseType);
+ }
+
+ if ("set".equals(r.containerType) && typeMapping.containsKey(r.containerType)) {
+ op.uniqueItems = true;
+ imports.add(typeMapping.get(r.containerType));
+ }
+
+ op.responses.add(r);
+ if (Boolean.TRUE.equals(r.isBinary) && Boolean.TRUE.equals(r.is2xx) && Boolean.FALSE.equals(op.isResponseBinary)) {
+ op.isResponseBinary = Boolean.TRUE;
+ }
+ if (Boolean.TRUE.equals(r.isFile) && Boolean.TRUE.equals(r.is2xx) && Boolean.FALSE.equals(op.isResponseFile)) {
+ op.isResponseFile = Boolean.TRUE;
+ }
+ if (Boolean.TRUE.equals(r.isDefault)) {
+ op.defaultReturnType = Boolean.TRUE;
+ }
+
+ // check if any 4xx or 5xx response has an error response object defined
+ if ((Boolean.TRUE.equals(r.is4xx) || Boolean.TRUE.equals(r.is5xx)) &&
+ Boolean.FALSE.equals(r.primitiveType) && Boolean.FALSE.equals(r.simpleType)) {
+ op.hasErrorResponseObject = Boolean.TRUE;
+ }
+ }
+
+ // check if the operation can both return a 2xx response with a body and without
+ if (op.responses.stream().anyMatch(response -> response.is2xx && response.dataType != null) &&
+ op.responses.stream().anyMatch(response -> response.is2xx && response.dataType == null)) {
+ op.isResponseOptional = Boolean.TRUE;
+ }
+
+ op.responses.sort((a, b) -> {
+ int aScore = a.isWildcard() ? 2 : a.isRange() ? 1 : 0;
+ int bScore = b.isWildcard() ? 2 : b.isRange() ? 1 : 0;
+ return Integer.compare(aScore, bScore);
+ });
+
+ if (methodResponse != null) {
+ handleMethodResponse(operation, schemas, op, methodResponse, importMapping);
+ }
+ }
+
+ // check skipOperationExample, which can be set to true to avoid out of memory errors for large spec
+ if (!isSkipOperationExample() && operation.getResponses() != null) {
+ // generate examples
+ ExampleGenerator generator = new ExampleGenerator(schemas, this.openAPI);
+ List> examples = new ArrayList<>();
+
+ for (String statusCode : operation.getResponses().keySet()) {
+ ApiResponse apiResponse = operation.getResponses().get(statusCode);
+ Schema schema = unaliasSchema(ModelUtils.getSchemaFromResponse(openAPI, apiResponse));
+ if (schema == null) {
+ continue;
+ }
+
+ if (apiResponse.getContent() != null) {
+ Set producesInfo = new ConcurrentSkipListSet<>(apiResponse.getContent().keySet());
+
+ String exampleStatusCode = statusCode;
+ if (exampleStatusCode.equals("default")) {
+ exampleStatusCode = "200";
+ }
+ List> examplesForResponse = generator.generateFromResponseSchema(exampleStatusCode, schema, producesInfo);
+ if (examplesForResponse != null) {
+ examples.addAll(examplesForResponse);
+ }
+ }
+ }
+ op.examples = examples;
+ }
+
+ if (operation.getCallbacks() != null && !operation.getCallbacks().isEmpty()) {
+ operation.getCallbacks().forEach((name, callback) -> {
+ CodegenCallback c = fromCallback(name, callback, servers);
+ op.callbacks.add(c);
+ });
+ }
+
+ List parameters = operation.getParameters();
+ List allParams = new ArrayList<>();
+ List bodyParams = new ArrayList<>();
+ List pathParams = new ArrayList<>();
+ List queryParams = new ArrayList<>();
+ List headerParams = new ArrayList<>();
+ List cookieParams = new ArrayList<>();
+ List formParams = new ArrayList<>();
+ List requiredParams = new ArrayList<>();
+ List optionalParams = new ArrayList<>();
+ List requiredAndNotNullableParams = new ArrayList<>();
+ List notNullableParams = new ArrayList<>();
+
+ CodegenParameter bodyParam = null;
+ RequestBody requestBody = ModelUtils.getReferencedRequestBody(this.openAPI, operation.getRequestBody());
+ if (requestBody != null) {
+ String contentType = getContentType(requestBody);
+ if (contentType != null) {
+ contentType = contentType.toLowerCase(Locale.ROOT);
+ }
+ if (contentType != null &&
+ ((!(this instanceof RustAxumServerCodegen) && contentType.startsWith("application/x-www-form-urlencoded")) ||
+ contentType.startsWith("multipart"))) {
+ // process form parameters
+ formParams = fromRequestBodyToFormParameters(requestBody, imports);
+ op.isMultipart = contentType.startsWith("multipart");
+ for (CodegenParameter cp : formParams) {
+ setParameterEncodingValues(cp, requestBody.getContent().get(contentType));
+ postProcessParameter(cp);
+ }
+ // add form parameters to the beginning of all parameter list
+ if (prependFormOrBodyParameters) {
+ for (CodegenParameter cp : formParams) {
+ allParams.add(cp.copy());
+ }
+ }
+ } else {
+ // process body parameter
+ String bodyParameterName = "";
+ if (op.vendorExtensions != null && op.vendorExtensions.containsKey("x-codegen-request-body-name")) {
+ bodyParameterName = (String) op.vendorExtensions.get("x-codegen-request-body-name");
+ }
+ if (requestBody.getExtensions() != null && requestBody.getExtensions().containsKey("x-codegen-request-body-name")) {
+ bodyParameterName = (String) requestBody.getExtensions().get("x-codegen-request-body-name");
+ }
+
+ bodyParam = fromRequestBody(requestBody, imports, bodyParameterName);
+
+ if (bodyParam != null) {
+ bodyParam.description = escapeText(requestBody.getDescription());
+ postProcessParameter(bodyParam);
+ bodyParams.add(bodyParam);
+ if (prependFormOrBodyParameters) {
+ allParams.add(bodyParam);
+ }
+
+ // add example
+ if (schemas != null && !isSkipOperationExample()) {
+ op.requestBodyExamples = new ExampleGenerator(schemas, this.openAPI).generate(null, new ArrayList<>(getConsumesInfo(this.openAPI, operation)), bodyParam.baseType);
+ }
+ }
+ }
+ }
+
+ if (parameters != null) {
+ for (Parameter param : parameters) {
+ param = ModelUtils.getReferencedParameter(this.openAPI, param);
+
+ CodegenParameter p = fromParameter(param, imports);
+ p.setContent(getContent(param.getContent(), imports, "RequestParameter" + toModelName(param.getName())));
+
+ // ensure unique params
+ if (ensureUniqueParams) {
+ while (!isParameterNameUnique(p, allParams)) {
+ p.paramName = generateNextName(p.paramName);
+ }
+ }
+
+ allParams.add(p);
+
+ if (param instanceof QueryParameter || "query".equalsIgnoreCase(param.getIn())) {
+ queryParams.add(p.copy());
+ } else if (param instanceof PathParameter || "path".equalsIgnoreCase(param.getIn())) {
+ pathParams.add(p.copy());
+ } else if (param instanceof HeaderParameter || "header".equalsIgnoreCase(param.getIn())) {
+ headerParams.add(p.copy());
+ } else if (param instanceof CookieParameter || "cookie".equalsIgnoreCase(param.getIn())) {
+ cookieParams.add(p.copy());
+ } else {
+ LOGGER.warn("Unknown parameter type {} for {}", p.baseType, p.baseName);
+ }
+
+ }
+ }
+
+ // add form/body parameter (if any) to the end of all parameter list
+ if (!prependFormOrBodyParameters) {
+ for (CodegenParameter cp : formParams) {
+ if (ensureUniqueParams) {
+ while (!isParameterNameUnique(cp, allParams)) {
+ cp.paramName = generateNextName(cp.paramName);
+ }
+ }
+ allParams.add(cp.copy());
+ }
+
+ for (CodegenParameter cp : bodyParams) {
+ if (ensureUniqueParams) {
+ while (!isParameterNameUnique(cp, allParams)) {
+ cp.paramName = generateNextName(cp.paramName);
+ }
+ }
+ allParams.add(cp.copy());
+ }
+ }
+
+ // create optional, required parameters
+ for (CodegenParameter cp : allParams) {
+ if (cp.required) { //required parameters
+ requiredParams.add(cp.copy());
+ } else { // optional parameters
+ optionalParams.add(cp.copy());
+ op.hasOptionalParams = true;
+ }
+
+ if (cp.requiredAndNotNullable()) {
+ requiredAndNotNullableParams.add(cp.copy());
+ }
+
+ if (!cp.isNullable) {
+ notNullableParams.add(cp.copy());
+ }
+ }
+
+ // add imports to operation import tag
+ for (String i : imports) {
+ if (needToImport(i)) {
+ op.imports.add(i);
+ }
+ }
+
+ op.bodyParam = bodyParam;
+ op.httpMethod = httpMethod.toUpperCase(Locale.ROOT);
+
+ // move "required" parameters in front of "optional" parameters
+ if (sortParamsByRequiredFlag) {
+ SortParametersByRequiredFlag(allParams);
+ }
+
+ op.allParams = allParams;
+ op.bodyParams = bodyParams;
+ op.pathParams = pathParams;
+ op.queryParams = queryParams;
+ op.headerParams = headerParams;
+ op.cookieParams = cookieParams;
+ op.formParams = formParams;
+ op.requiredParams = requiredParams;
+ op.optionalParams = optionalParams;
+ op.requiredAndNotNullableParams = requiredAndNotNullableParams;
+ op.notNullableParams = notNullableParams;
+ op.externalDocs = operation.getExternalDocs();
+ // legacy support
+ op.nickname = op.operationId;
+
+ if (op.allParams.size() > 0) {
+ op.hasParams = true;
+ }
+ op.hasRequiredParams = op.requiredParams.size() > 0;
+
+ // set Restful Flag
+ op.isRestfulShow = op.isRestfulShow();
+ op.isRestfulIndex = op.isRestfulIndex();
+ op.isRestfulCreate = op.isRestfulCreate();
+ op.isRestfulUpdate = op.isRestfulUpdate();
+ op.isRestfulDestroy = op.isRestfulDestroy();
+ op.isRestful = op.isRestful();
+
+ return op;
+ }
+
+ public void SortParametersByRequiredFlag(List parameters) {
+ Collections.sort(parameters, 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;
+ }
+ });
+ }
+
+ public boolean isParameterNameUnique(CodegenParameter p, List parameters) {
+ for (CodegenParameter parameter : parameters) {
+ if (System.identityHashCode(p) == System.identityHashCode(parameter)) {
+ continue; // skip itself
+ }
+
+ if (p.paramName.equals(parameter.paramName)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Convert OAS Response object to Codegen Response object
+ *
+ * @param responseCode HTTP response code
+ * @param response OAS Response object
+ * @return Codegen Response object
+ */
+ public CodegenResponse fromResponse(String responseCode, ApiResponse response) {
+ CodegenResponse r = CodegenModelFactory.newInstance(CodegenModelType.RESPONSE);
+
+ if ("default".equals(responseCode) || "defaultResponse".equals(responseCode)) {
+ r.code = "0";
+ r.isDefault = true;
+ } else {
+ r.code = responseCode;
+ switch (r.code.charAt(0)) {
+
+ case '1':
+ r.is1xx = true;
+ break;
+ case '2':
+ r.is2xx = true;
+ break;
+ case '3':
+ r.is3xx = true;
+ break;
+ case '4':
+ r.is4xx = true;
+ break;
+ case '5':
+ r.is5xx = true;
+ break;
+ default:
+ throw new RuntimeException("Invalid response code " + responseCode);
+ }
+ }
+
+ Schema responseSchema;
+ if (this.openAPI != null && this.openAPI.getComponents() != null) {
+ responseSchema = unaliasSchema(ModelUtils.getSchemaFromResponse(openAPI, response));
+ } else { // no model/alias defined
+ responseSchema = ModelUtils.getSchemaFromResponse(openAPI, response);
+ }
+ r.schema = responseSchema;
+ r.message = escapeText(response.getDescription());
+ // TODO need to revise and test examples in responses
+ // ApiResponse does not support examples at the moment
+ //r.examples = toExamples(response.getExamples());
+ r.jsonSchema = Json.pretty(response);
+ if (response.getExtensions() != null && !response.getExtensions().isEmpty()) {
+ r.vendorExtensions.putAll(response.getExtensions());
+ }
+ addHeaders(response, r.headers);
+ r.hasHeaders = !r.headers.isEmpty();
+
+ if (r.schema == null) {
+ r.primitiveType = true;
+ r.simpleType = true;
+ return r;
+ }
+
+ ModelUtils.syncValidationProperties(responseSchema, r);
+ if (responseSchema.getPattern() != null) {
+ r.setPattern(toRegularExpression(responseSchema.getPattern()));
+ }
+
+ CodegenProperty cp = fromProperty("response", responseSchema, false);
+ r.dataType = getTypeDeclaration(responseSchema);
+ r.returnProperty = cp;
+
+ if (!ModelUtils.isArraySchema(responseSchema)) {
+ if (cp.complexType != null) {
+ if (cp.items != null) {
+ r.baseType = cp.items.complexType;
+ } else {
+ r.baseType = cp.complexType;
+ }
+ r.isModel = true;
+ } else {
+ r.baseType = cp.baseType;
+ }
+ }
+
+ r.setTypeProperties(responseSchema);
+ r.setComposedSchemas(getComposedSchemas(responseSchema));
+ if (ModelUtils.isArraySchema(responseSchema)) {
+ r.simpleType = false;
+ r.isArray = true;
+ r.containerType = cp.containerType;
+ r.containerTypeMapped = typeMapping.get(cp.containerType);
+ CodegenProperty items = fromProperty("response", ModelUtils.getSchemaItems(responseSchema), false);
+ r.setItems(items);
+ CodegenProperty innerCp = items;
+
+ while (innerCp != null) {
+ r.baseType = innerCp.baseType;
+ innerCp = innerCp.items;
+ }
+ } else if (ModelUtils.isFileSchema(responseSchema) && !ModelUtils.isStringSchema(responseSchema)) {
+ // swagger v2 only, type file
+ r.isFile = true;
+ } else if (ModelUtils.isStringSchema(responseSchema)) {
+ if (ModelUtils.isEmailSchema(responseSchema)) {
+ r.isEmail = true;
+ } else if (ModelUtils.isPasswordSchema(responseSchema)) {
+ r.isPassword = true;
+ } else if (ModelUtils.isUUIDSchema(responseSchema)) {
+ r.isUuid = true;
+ } else if (ModelUtils.isByteArraySchema(responseSchema)) {
+ r.setIsString(false);
+ r.isByteArray = true;
+ } else if (ModelUtils.isBinarySchema(responseSchema)) {
+ r.isFile = true; // file = binary in OAS3
+ r.isBinary = true;
+ } else if (ModelUtils.isDateSchema(responseSchema)) {
+ r.setIsString(false); // for backward compatibility with 2.x
+ r.isDate = true;
+ } else if (ModelUtils.isDateTimeSchema(responseSchema)) {
+ r.setIsString(false); // for backward compatibility with 2.x
+ r.isDateTime = true;
+ } else if (ModelUtils.isDecimalSchema(responseSchema)) { // type: string, format: number
+ r.isDecimal = true;
+ r.setIsString(false);
+ r.isNumeric = true;
+ }
+ } else if (ModelUtils.isIntegerSchema(responseSchema)) { // integer type
+ r.isNumeric = Boolean.TRUE;
+ if (ModelUtils.isLongSchema(responseSchema)) { // int64/long format
+ r.isLong = Boolean.TRUE;
+ } else {
+ r.isInteger = Boolean.TRUE; // older use case, int32 and unbounded int
+ if (ModelUtils.isShortSchema(responseSchema)) { // int32
+ r.setIsShort(Boolean.TRUE);
+ }
+ }
+ } else if (ModelUtils.isNumberSchema(responseSchema)) {
+ r.isNumeric = Boolean.TRUE;
+ if (ModelUtils.isFloatSchema(responseSchema)) { // float
+ r.isFloat = Boolean.TRUE;
+ } else if (ModelUtils.isDoubleSchema(responseSchema)) { // double
+ r.isDouble = Boolean.TRUE;
+ }
+ } else if (ModelUtils.isTypeObjectSchema(responseSchema)) {
+ if (ModelUtils.isFreeFormObject(responseSchema)) {
+ r.isFreeFormObject = true;
+ } else {
+ r.isModel = true;
+ }
+ r.simpleType = false;
+ r.containerType = cp.containerType;
+ r.containerTypeMapped = cp.containerTypeMapped;
+ addVarsRequiredVarsAdditionalProps(responseSchema, r);
+ } else if (ModelUtils.isAnyType(responseSchema)) {
+ addVarsRequiredVarsAdditionalProps(responseSchema, r);
+ } else if (!ModelUtils.isBooleanSchema(responseSchema)) {
+ // referenced schemas
+ LOGGER.debug("Property type is not primitive: {}", cp.dataType);
+ }
+
+ r.primitiveType = (r.baseType == null || languageSpecificPrimitives().contains(r.baseType));
+
+ if (r.baseType == null) {
+ r.isMap = false;
+ r.isArray = false;
+ r.primitiveType = true;
+ r.simpleType = true;
+ }
+
+ postProcessResponseWithProperty(r, cp);
+ return r;
+ }
+
+ /**
+ * Convert OAS Callback object to Codegen Callback object
+ *
+ * @param name callback name
+ * @param callback OAS Callback object
+ * @param servers list of servers
+ * @return Codegen Response object
+ */
+ public CodegenCallback fromCallback(String name, Callback callback, List servers) {
+ CodegenCallback c = new CodegenCallback();
+ c.name = name;
+
+ if (callback.getExtensions() != null && !callback.getExtensions().isEmpty()) {
+ c.vendorExtensions.putAll(callback.getExtensions());
+ }
+
+ callback.forEach((expression, pi) -> {
+ CodegenCallback.Url u = new CodegenCallback.Url();
+ u.expression = expression;
+
+ if (pi.getExtensions() != null && !pi.getExtensions().isEmpty()) {
+ u.vendorExtensions.putAll(pi.getExtensions());
+ }
+
+ Stream.of(
+ Pair.of("get", pi.getGet()),
+ Pair.of("head", pi.getHead()),
+ Pair.of("put", pi.getPut()),
+ Pair.of("post", pi.getPost()),
+ Pair.of("delete", pi.getDelete()),
+ Pair.of("patch", pi.getPatch()),
+ Pair.of("options", pi.getOptions()))
+ .filter(p -> p.getValue() != null)
+ .forEach(p -> {
+ String method = p.getKey();
+ Operation op = p.getValue();
+
+ if (op.getExtensions() != null && Boolean.TRUE.equals(op.getExtensions().get("x-internal"))) {
+ // skip operation if x-internal sets to true
+ LOGGER.info("Operation ({} {} - {}) not generated since x-internal is set to true",
+ method, expression, op.getOperationId());
+ } else {
+ boolean genId = op.getOperationId() == null;
+ if (genId) {
+ op.setOperationId(getOrGenerateOperationId(op, c.name + "_" + expression.replaceAll("\\{\\$.*}", ""), method));
+ }
+
+ if (op.getExtensions() == null) {
+ op.setExtensions(new HashMap<>());
+ }
+ // This extension will be removed later by `fromOperation()` as it is only needed here to
+ // distinguish between normal operations and callback requests
+ op.getExtensions().put("x-callback-request", true);
+
+ CodegenOperation co = fromOperation(expression, method, op, servers);
+ if (genId) {
+ co.operationIdOriginal = null;
+ // legacy (see `fromOperation()`)
+ co.nickname = co.operationId;
+ }
+ u.requests.add(co);
+ }
+ });
+
+ c.urls.add(u);
+ });
+
+ return c;
+ }
+
+ private void finishUpdatingParameter(CodegenParameter codegenParameter, Parameter parameter) {
+ // default to UNKNOWN_PARAMETER_NAME if paramName is null
+ if (codegenParameter.paramName == null) {
+ LOGGER.warn("Parameter name not defined properly. Default to UNKNOWN_PARAMETER_NAME");
+ codegenParameter.paramName = "UNKNOWN_PARAMETER_NAME";
+ }
+
+ // set the parameter example value
+ // should be overridden by lang codegen
+ setParameterExampleValue(codegenParameter, parameter);
+ // set the parameter examples (if available)
+ setParameterExamples(codegenParameter, parameter);
+
+ postProcessParameter(codegenParameter);
+ LOGGER.debug("debugging codegenParameter return: {}", codegenParameter);
+ }
+
+
+ private void updateParameterForMap(CodegenParameter codegenParameter, Schema parameterSchema, Set imports) {
+ CodegenProperty codegenProperty = fromProperty("inner", ModelUtils.getAdditionalProperties(parameterSchema), false);
+ codegenParameter.items = codegenProperty;
+ codegenParameter.mostInnerItems = codegenProperty.mostInnerItems;
+ codegenParameter.baseType = codegenProperty.dataType;
+ codegenParameter.isContainer = true;
+ codegenParameter.isMap = true;
+
+ // recursively add import
+ while (codegenProperty != null) {
+ imports.add(codegenProperty.baseType);
+ codegenProperty = codegenProperty.items;
+ }
+ }
+
+ protected void updateParameterForString(CodegenParameter codegenParameter, Schema parameterSchema) {
+ if (ModelUtils.isEmailSchema(parameterSchema)) {
+ codegenParameter.isEmail = true;
+ } else if (ModelUtils.isUUIDSchema(parameterSchema)) {
+ codegenParameter.isUuid = true;
+ } else if (ModelUtils.isByteArraySchema(parameterSchema)) {
+ codegenParameter.setIsString(false);
+ codegenParameter.isByteArray = true;
+ codegenParameter.isPrimitiveType = true;
+ } else if (ModelUtils.isBinarySchema(parameterSchema)) {
+ codegenParameter.isBinary = true;
+ codegenParameter.isFile = true; // file = binary in OAS3
+ codegenParameter.isPrimitiveType = true;
+ } else if (ModelUtils.isDateSchema(parameterSchema)) {
+ codegenParameter.setIsString(false); // for backward compatibility with 2.x
+ codegenParameter.isDate = true;
+ codegenParameter.isPrimitiveType = true;
+ } else if (ModelUtils.isDateTimeSchema(parameterSchema)) {
+ codegenParameter.setIsString(false); // for backward compatibility with 2.x
+ codegenParameter.isDateTime = true;
+ codegenParameter.isPrimitiveType = true;
+ } else if (ModelUtils.isDecimalSchema(parameterSchema)) { // type: string, format: number
+ codegenParameter.setIsString(false);
+ codegenParameter.isDecimal = true;
+ codegenParameter.isPrimitiveType = true;
+ }
+ if (Boolean.TRUE.equals(codegenParameter.isString)) {
+ codegenParameter.isPrimitiveType = true;
+ }
+ }
+
+ /**
+ * Convert OAS Parameter object to Codegen Parameter object
+ *
+ * @param parameter OAS parameter object
+ * @param imports set of imports for library/package/module
+ * @return Codegen Parameter object
+ */
+ public CodegenParameter fromParameter(Parameter parameter, Set imports) {
+ CodegenParameter codegenParameter = CodegenModelFactory.newInstance(CodegenModelType.PARAMETER);
+
+ codegenParameter.baseName = parameter.getName();
+ codegenParameter.description = escapeText(parameter.getDescription());
+ codegenParameter.unescapedDescription = parameter.getDescription();
+ if (parameter.getRequired() != null) {
+ codegenParameter.required = parameter.getRequired();
+ }
+ if (parameter.getDeprecated() != null) {
+ codegenParameter.isDeprecated = parameter.getDeprecated();
+ }
+ codegenParameter.jsonSchema = Json.pretty(parameter);
+
+ if (GlobalSettings.getProperty("debugParser") != null) {
+ LOGGER.info("working on Parameter {}", parameter.getName());
+ LOGGER.info("JSON schema: {}", codegenParameter.jsonSchema);
+ }
+
+ if (parameter.getExtensions() != null && !parameter.getExtensions().isEmpty()) {
+ codegenParameter.vendorExtensions.putAll(parameter.getExtensions());
+ }
+ if (parameter.getSchema() != null && parameter.getSchema().getExtensions() != null && !parameter.getSchema().getExtensions().isEmpty()) {
+ codegenParameter.vendorExtensions.putAll(parameter.getSchema().getExtensions());
+ }
+
+ Schema parameterSchema;
+
+ // the parameter model name is obtained from the schema $ref
+ // e.g. #/components/schemas/list_pageQuery_parameter => toModelName(list_pageQuery_parameter)
+ String parameterModelName = null;
+
+ if (parameter.getSchema() != null) {
+ parameterSchema = unaliasSchema(parameter.getSchema());
+ parameterModelName = getParameterDataType(parameter, parameterSchema);
+ CodegenProperty prop;
+ if (this instanceof RustServerCodegen) {
+ // for rust server, we need to do somethings special as it uses
+ // $ref (e.g. #components/schemas/Pet) to determine whether it's a model
+ prop = fromProperty(parameter.getName(), parameterSchema, false);
+ } else if (getUseInlineModelResolver()) {
+ prop = fromProperty(parameter.getName(), getReferencedSchemaWhenNotEnum(parameterSchema), false);
+ } else {
+ prop = fromProperty(parameter.getName(), parameterSchema, false);
+ }
+ codegenParameter.setSchema(prop);
+ } else if (parameter.getContent() != null) {
+ Content content = parameter.getContent();
+ if (content.size() > 1) {
+ once(LOGGER).warn("Multiple schemas found in content, returning only the first one");
+ }
+ Entry entry = content.entrySet().iterator().next();
+ codegenParameter.contentType = entry.getKey();
+ parameterSchema = entry.getValue().getSchema();
+ parameterModelName = getParameterDataType(parameter, parameterSchema);
+ } else {
+ parameterSchema = null;
+ }
+
+ if (parameter instanceof QueryParameter || "query".equalsIgnoreCase(parameter.getIn())) {
+ codegenParameter.isQueryParam = true;
+ codegenParameter.isAllowEmptyValue = parameter.getAllowEmptyValue() != null && parameter.getAllowEmptyValue();
+ } else if (parameter instanceof PathParameter || "path".equalsIgnoreCase(parameter.getIn())) {
+ codegenParameter.required = true;
+ codegenParameter.isPathParam = true;
+ } else if (parameter instanceof HeaderParameter || "header".equalsIgnoreCase(parameter.getIn())) {
+ codegenParameter.isHeaderParam = true;
+ } else if (parameter instanceof CookieParameter || "cookie".equalsIgnoreCase(parameter.getIn())) {
+ codegenParameter.isCookieParam = true;
+ } else {
+ LOGGER.warn("Unknown parameter type: {}", parameter.getName());
+ }
+
+ if (parameterSchema == null) {
+ LOGGER.error("Not handling {} as Body Parameter at the moment", parameter);
+ finishUpdatingParameter(codegenParameter, parameter);
+ return codegenParameter;
+ }
+
+ // TODO need to review replacing empty map with schemaMapping instead
+ parameterSchema = unaliasSchema(parameterSchema);
+ if (parameterSchema == null) {
+ LOGGER.warn("warning! Schema not found for parameter \" {} \"", parameter.getName());
+ finishUpdatingParameter(codegenParameter, parameter);
+ return codegenParameter;
+ }
+
+ if (getUseInlineModelResolver() && !(this instanceof RustServerCodegen)) {
+ // for rust server, we cannot run the following as it uses
+ // $ref (e.g. #components/schemas/Pet) to determine whether it's a model
+ parameterSchema = getReferencedSchemaWhenNotEnum(parameterSchema);
+ }
+
+ ModelUtils.syncValidationProperties(parameterSchema, codegenParameter);
+ codegenParameter.setTypeProperties(parameterSchema);
+ codegenParameter.setComposedSchemas(getComposedSchemas(parameterSchema));
+
+ if (Boolean.TRUE.equals(parameterSchema.getNullable())) { // use nullable defined in the spec
+ codegenParameter.isNullable = true;
+ }
+
+ if (parameter.getStyle() != null) {
+ codegenParameter.style = parameter.getStyle().toString();
+ codegenParameter.isDeepObject = Parameter.StyleEnum.DEEPOBJECT == parameter.getStyle();
+ codegenParameter.isMatrix = Parameter.StyleEnum.MATRIX == parameter.getStyle();
+ }
+
+ // the default value is false
+ // https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#user-content-parameterexplode
+ codegenParameter.isExplode = parameter.getExplode() != null && parameter.getExplode();
+
+ // TODO revise collectionFormat, default collection format in OAS 3 appears to multi at least for query parameters
+ // https://swagger.io/docs/specification/serialization/
+ String collectionFormat = null;
+
+ if (ModelUtils.isFileSchema(parameterSchema) && !ModelUtils.isStringSchema(parameterSchema)) {
+ // swagger v2 only, type file
+ codegenParameter.isFile = true;
+ } else if (ModelUtils.isStringSchema(parameterSchema)) {
+ updateParameterForString(codegenParameter, parameterSchema);
+ } else if (ModelUtils.isBooleanSchema(parameterSchema)) {
+ codegenParameter.isPrimitiveType = true;
+ } else if (ModelUtils.isNumberSchema(parameterSchema)) {
+ codegenParameter.isPrimitiveType = true;
+ if (ModelUtils.isFloatSchema(parameterSchema)) { // float
+ codegenParameter.isFloat = true;
+ } else if (ModelUtils.isDoubleSchema(parameterSchema)) { // double
+ codegenParameter.isDouble = true;
+ }
+ } else if (ModelUtils.isIntegerSchema(parameterSchema)) { // integer type
+ codegenParameter.isPrimitiveType = true;
+ if (ModelUtils.isLongSchema(parameterSchema)) { // int64/long format
+ codegenParameter.isLong = true;
+ } else {
+ codegenParameter.isInteger = true;
+ if (ModelUtils.isShortSchema(parameterSchema)) { // int32/short format
+ codegenParameter.isShort = true;
+ } else { // unbounded integer
+ }
+ }
+ } else if (ModelUtils.isTypeObjectSchema(parameterSchema)) {
+ if (ModelUtils.isMapSchema(parameterSchema)) { // for map parameter
+ updateParameterForMap(codegenParameter, parameterSchema, imports);
+ }
+ if (ModelUtils.isFreeFormObject(parameterSchema)) {
+ codegenParameter.isFreeFormObject = true;
+ }
+ addVarsRequiredVarsAdditionalProps(parameterSchema, codegenParameter);
+ } else if (ModelUtils.isNullType(parameterSchema)) {
+ } else if (ModelUtils.isAnyType(parameterSchema)) {
+ // any schema with no type set, composed schemas often do this
+ if (ModelUtils.isMapSchema(parameterSchema)) { // for map parameter
+ updateParameterForMap(codegenParameter, parameterSchema, imports);
+ }
+ addVarsRequiredVarsAdditionalProps(parameterSchema, codegenParameter);
+ } else if (ModelUtils.isArraySchema(parameterSchema)) {
+ Schema inner = ModelUtils.getSchemaItems(parameterSchema);
+
+ collectionFormat = getCollectionFormat(parameter);
+ // default to csv:
+ collectionFormat = StringUtils.isEmpty(collectionFormat) ? "csv" : collectionFormat;
+ CodegenProperty itemsProperty = fromProperty("inner", inner, false);
+ codegenParameter.items = itemsProperty;
+ codegenParameter.mostInnerItems = itemsProperty.mostInnerItems;
+ codegenParameter.baseType = itemsProperty.dataType;
+ codegenParameter.isContainer = true;
+ // recursively add import
+ while (itemsProperty != null) {
+ imports.add(itemsProperty.baseType);
+ itemsProperty = itemsProperty.items;
+ }
+ } else {
+ // referenced schemas
+ }
+
+ CodegenProperty codegenProperty = fromProperty(parameter.getName(), parameterSchema, false);
+ if (Boolean.TRUE.equals(codegenProperty.isModel)) {
+ codegenParameter.isModel = true;
+ }
+
+ if (parameterModelName != null) {
+ codegenParameter.dataType = parameterModelName;
+ if (ModelUtils.isObjectSchema(parameterSchema) || ModelUtils.isComposedSchema(parameterSchema)) {
+ codegenProperty.complexType = codegenParameter.dataType;
+ }
+ } else {
+ codegenParameter.dataType = codegenProperty.dataType;
+ }
+
+ if (ModelUtils.isArraySchema(parameterSchema)) {
+ imports.add(codegenProperty.baseType);
+ }
+
+ codegenParameter.dataFormat = codegenProperty.dataFormat;
+ if (parameter.getRequired() != null) {
+ codegenParameter.required = parameter.getRequired().booleanValue();
+ }
+
+ // set containerType
+ codegenParameter.containerType = codegenProperty.containerType;
+ codegenParameter.containerTypeMapped = codegenProperty.containerTypeMapped;
+
+ // enum
+ updateCodegenPropertyEnum(codegenProperty);
+ codegenParameter.isEnum = codegenProperty.isEnum;
+ codegenParameter.isEnumRef = codegenProperty.isEnumRef;
+ codegenParameter._enum = codegenProperty._enum;
+ codegenParameter.allowableValues = codegenProperty.allowableValues;
+
+ if (codegenProperty.isEnum) {
+ codegenParameter.datatypeWithEnum = codegenProperty.datatypeWithEnum;
+ codegenParameter.enumName = codegenProperty.enumName;
+ if (codegenProperty.defaultValue != null) {
+ codegenParameter.enumDefaultValue = codegenProperty.defaultValue.replace(codegenProperty.enumName + ".", "");
+ }
+ }
+
+ if (codegenProperty.items != null && codegenProperty.items.isEnum) {
+ codegenParameter.datatypeWithEnum = codegenProperty.datatypeWithEnum;
+ codegenParameter.enumName = codegenProperty.enumName;
+ codegenParameter.items = codegenProperty.items;
+ codegenParameter.mostInnerItems = codegenProperty.mostInnerItems;
+ }
+
+ codegenParameter.collectionFormat = collectionFormat;
+ if ("multi".equals(collectionFormat)) {
+ codegenParameter.isCollectionFormatMulti = true;
+ }
+ codegenParameter.paramName = toParamName(parameter.getName());
+ codegenParameter.nameInCamelCase = camelize(codegenParameter.paramName, LOWERCASE_FIRST_LETTER);
+ codegenParameter.nameInPascalCase = camelize(codegenParameter.paramName);
+ codegenParameter.nameInSnakeCase = CaseFormat.UPPER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, codegenParameter.nameInPascalCase);
+ codegenParameter.nameInLowerCase = codegenParameter.paramName.toLowerCase(Locale.ROOT);
+
+ // import
+ if (codegenProperty.complexType != null) {
+ imports.add(codegenProperty.complexType);
+ }
+
+ codegenParameter.pattern = toRegularExpression(parameterSchema.getPattern());
+
+ if (codegenParameter.isQueryParam && codegenParameter.isDeepObject && loadDeepObjectIntoItems) {
+ Schema schema = parameterSchema;
+ if (schema.get$ref() != null) {
+ schema = ModelUtils.getReferencedSchema(openAPI, schema);
+ }
+ codegenParameter.items = fromProperty(codegenParameter.paramName, schema, false);
+ // https://swagger.io/docs/specification/serialization/
+ if (schema != null) {
+ Map> properties = schema.getProperties();
+ List requiredVarNames = new ArrayList<>();
+ if (schema.getRequired() != null) {
+ requiredVarNames.addAll(schema.getRequired());
+ }
+ if (properties != null) {
+ codegenParameter.items.vars =
+ properties.entrySet().stream()
+ .map(entry -> {
+ CodegenProperty property = fromProperty(entry.getKey(), entry.getValue(), requiredVarNames.contains(entry.getKey()));
+ return property;
+ }).collect(Collectors.toList());
+ } else {
+ //LOGGER.error("properties is null: {}", schema);
+ }
+ } else {
+ LOGGER.warn(
+ "No object schema found for deepObject parameter{} deepObject won't have specific properties",
+ codegenParameter);
+ }
+ }
+
+ // set default value
+ codegenParameter.defaultValue = toDefaultParameterValue(codegenProperty, parameterSchema);
+
+ finishUpdatingParameter(codegenParameter, parameter);
+ return codegenParameter;
+ }
+
+ private Schema getReferencedSchemaWhenNotEnum(Schema parameterSchema) {
+ Schema referencedSchema = ModelUtils.getReferencedSchema(openAPI, parameterSchema);
+ if (referencedSchema.getEnum() != null && !referencedSchema.getEnum().isEmpty()) {
+ referencedSchema = parameterSchema;
+ }
+ return referencedSchema;
+ }
+
+ /**
+ * Returns the data type of parameter.
+ * Returns null by default to use the CodegenProperty.datatype value
+ *
+ * @param parameter Parameter
+ * @param schema Schema
+ * @return data type
+ */
+ protected String getParameterDataType(Parameter parameter, Schema schema) {
+ Schema unaliasSchema = unaliasSchema(schema);
+ if (unaliasSchema.get$ref() != null) {
+ return toModelName(ModelUtils.getSimpleRef(unaliasSchema.get$ref()));
+ }
+ return null;
+ }
+
+ // TODO revise below as it should be replaced by ModelUtils.isByteArraySchema(parameterSchema)
+ public boolean isDataTypeBinary(String dataType) {
+ if (dataType != null) {
+ return dataType.toLowerCase(Locale.ROOT).startsWith("byte");
+ } else {
+ return false;
+ }
+ }
+
+ // TODO revise below as it should be replaced by ModelUtils.isFileSchema(parameterSchema)
+ public boolean isDataTypeFile(String dataType) {
+ if (dataType != null) {
+ return dataType.toLowerCase(Locale.ROOT).equals("file");
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Convert map of OAS SecurityScheme objects to a list of Codegen Security objects
+ *
+ * @param securitySchemeMap a map of OAS SecuritySchemeDefinition object
+ * @return a list of Codegen Security objects
+ */
+ @Override
+ @SuppressWarnings("static-method")
+ public List fromSecurity(Map securitySchemeMap) {
+ if (securitySchemeMap == null) {
+ return Collections.emptyList();
+ }
+
+ List codegenSecurities = new ArrayList<>(securitySchemeMap.size());
+ for (String key : securitySchemeMap.keySet()) {
+ final SecurityScheme securityScheme = securitySchemeMap.get(key);
+ if (SecurityScheme.Type.APIKEY.equals(securityScheme.getType())) {
+ final CodegenSecurity cs = defaultCodegenSecurity(key, securityScheme);
+ cs.isBasic = cs.isOAuth = cs.isOpenId = false;
+ cs.isApiKey = true;
+ cs.keyParamName = securityScheme.getName();
+ cs.isKeyInHeader = securityScheme.getIn() == SecurityScheme.In.HEADER;
+ cs.isKeyInQuery = securityScheme.getIn() == SecurityScheme.In.QUERY;
+ cs.isKeyInCookie = securityScheme.getIn() == SecurityScheme.In.COOKIE; //it assumes a validation step prior to generation. (cookie-auth supported from OpenAPI 3.0.0)
+ codegenSecurities.add(cs);
+ } else if (SecurityScheme.Type.HTTP.equals(securityScheme.getType())) {
+ final CodegenSecurity cs = defaultCodegenSecurity(key, securityScheme);
+ cs.isKeyInHeader = cs.isKeyInQuery = cs.isKeyInCookie = cs.isApiKey = cs.isOAuth = cs.isOpenId = false;
+ cs.isBasic = true;
+ if ("basic".equalsIgnoreCase(securityScheme.getScheme())) {
+ cs.isBasicBasic = true;
+ } else if ("bearer".equalsIgnoreCase(securityScheme.getScheme())) {
+ cs.isBasicBearer = true;
+ cs.bearerFormat = securityScheme.getBearerFormat();
+ } else if ("signature".equalsIgnoreCase(securityScheme.getScheme())) {
+ // HTTP signature as defined in https://datatracker.ietf.org/doc/draft-cavage-http-signatures/
+ // The registry of security schemes is maintained by IANA.
+ // https://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml
+ // As of January 2020, the "signature" scheme has not been registered with IANA yet.
+ // This scheme may have to be changed when it is officially registered with IANA.
+ cs.isHttpSignature = true;
+ once(LOGGER).warn("Security scheme 'HTTP signature' is a draft IETF RFC and subject to change.");
+ } else {
+ once(LOGGER).warn("Unknown scheme `{}` found in the HTTP security definition.", securityScheme.getScheme());
+ }
+ codegenSecurities.add(cs);
+ } else if (SecurityScheme.Type.OAUTH2.equals(securityScheme.getType())) {
+ final OAuthFlows flows = securityScheme.getFlows();
+ boolean isFlowEmpty = true;
+ if (securityScheme.getFlows() == null) {
+ throw new RuntimeException("missing oauth flow in " + key);
+ }
+ if (flows.getPassword() != null) {
+ final CodegenSecurity cs = defaultOauthCodegenSecurity(key, securityScheme);
+ setOauth2Info(cs, flows.getPassword());
+ cs.isPassword = true;
+ cs.flow = "password";
+ codegenSecurities.add(cs);
+ isFlowEmpty = false;
+ }
+ if (flows.getImplicit() != null) {
+ final CodegenSecurity cs = defaultOauthCodegenSecurity(key, securityScheme);
+ setOauth2Info(cs, flows.getImplicit());
+ cs.isImplicit = true;
+ cs.flow = "implicit";
+ codegenSecurities.add(cs);
+ isFlowEmpty = false;
+ }
+ if (flows.getClientCredentials() != null) {
+ final CodegenSecurity cs = defaultOauthCodegenSecurity(key, securityScheme);
+ setOauth2Info(cs, flows.getClientCredentials());
+ cs.isApplication = true;
+ cs.flow = "application";
+ codegenSecurities.add(cs);
+ isFlowEmpty = false;
+ }
+ if (flows.getAuthorizationCode() != null) {
+ final CodegenSecurity cs = defaultOauthCodegenSecurity(key, securityScheme);
+ setOauth2Info(cs, flows.getAuthorizationCode());
+ cs.isCode = true;
+ cs.flow = "accessCode";
+ codegenSecurities.add(cs);
+ isFlowEmpty = false;
+ }
+
+ if (isFlowEmpty) {
+ once(LOGGER).error("Invalid flow definition defined in the security scheme: {}", flows);
+ }
+ } else if (SecurityScheme.Type.OPENIDCONNECT.equals(securityScheme.getType())) {
+ final CodegenSecurity cs = defaultCodegenSecurity(key, securityScheme);
+ cs.isKeyInHeader = cs.isKeyInQuery = cs.isKeyInCookie = cs.isApiKey = cs.isBasic = false;
+ cs.isOpenId = true;
+ cs.openIdConnectUrl = securityScheme.getOpenIdConnectUrl();
+ if (securityScheme.getFlows() != null) {
+ setOpenIdConnectInfo(cs, securityScheme.getFlows().getAuthorizationCode());
+ }
+ codegenSecurities.add(cs);
+ } else {
+ once(LOGGER).error("Unknown type `{}` found in the security definition `{}`.", securityScheme.getType(), securityScheme.getName());
+ }
+ }
+
+ return codegenSecurities;
+ }
+
+ private CodegenSecurity defaultCodegenSecurity(String key, SecurityScheme securityScheme) {
+ final CodegenSecurity cs = CodegenModelFactory.newInstance(CodegenModelType.SECURITY);
+ cs.name = key;
+ cs.description = securityScheme.getDescription();
+ cs.type = securityScheme.getType().toString();
+ cs.isCode = cs.isPassword = cs.isApplication = cs.isImplicit = cs.isOpenId = false;
+ cs.isHttpSignature = false;
+ cs.isBasicBasic = cs.isBasicBearer = false;
+ cs.scheme = securityScheme.getScheme();
+ if (securityScheme.getExtensions() != null) {
+ cs.vendorExtensions.putAll(securityScheme.getExtensions());
+ }
+ return cs;
+ }
+
+ private CodegenSecurity defaultOauthCodegenSecurity(String key, SecurityScheme securityScheme) {
+ final CodegenSecurity cs = defaultCodegenSecurity(key, securityScheme);
+ cs.isKeyInHeader = cs.isKeyInQuery = cs.isKeyInCookie = cs.isApiKey = cs.isBasic = cs.isOpenId = false;
+ cs.isOAuth = true;
+ return cs;
+ }
+
+ protected void setReservedWordsLowerCase(List words) {
+ reservedWords = new HashSet<>();
+ for (String word : words) {
+ reservedWords.add(word.toLowerCase(Locale.ROOT));
+ }
+ }
+
+ protected boolean isReservedWord(String word) {
+ return word != null && reservedWords.contains(word.toLowerCase(Locale.ROOT));
+ }
+
+ /**
+ * Get operationId from the operation object, and if it's blank, generate a new one from the given parameters.
+ *
+ * @param operation the operation object
+ * @param path the path of the operation
+ * @param httpMethod the HTTP method of the operation
+ * @return the (generated) operationId
+ */
+ protected String getOrGenerateOperationId(Operation operation, String path, String httpMethod) {
+ String operationId = operation.getOperationId();
+ if (StringUtils.isBlank(operationId)) {
+ String tmpPath = path;
+ tmpPath = tmpPath.replaceAll("\\{", "");
+ tmpPath = tmpPath.replaceAll("\\}", "");
+ String[] parts = (tmpPath + "/" + httpMethod).split("/");
+ StringBuilder builder = new StringBuilder();
+ if ("/".equals(tmpPath)) {
+ // must be root tmpPath
+ builder.append("root");
+ }
+ for (String part : parts) {
+ if (part.length() > 0) {
+ if (builder.toString().length() == 0) {
+ part = Character.toLowerCase(part.charAt(0)) + part.substring(1);
+ } else {
+ part = camelize(part);
+ }
+ builder.append(part);
+ }
+ }
+ operationId = sanitizeName(builder.toString());
+ LOGGER.warn("Empty operationId found for path: {} {}. Renamed to auto-generated operationId: {}", httpMethod, path, operationId);
+ }
+
+ // remove prefix in operationId
+ if (removeOperationIdPrefix) {
+ // The prefix is everything before the removeOperationIdPrefixCount occurrence of removeOperationIdPrefixDelimiter
+ String[] components = operationId.split("[" + removeOperationIdPrefixDelimiter + "]");
+ if (components.length > 1) {
+ // If removeOperationIdPrefixCount is -1 or bigger that the number of occurrences, uses the last one
+ int component_number = removeOperationIdPrefixCount == -1 ? components.length - 1 : removeOperationIdPrefixCount;
+ component_number = Math.min(component_number, components.length - 1);
+ // Reconstruct the operationId from its split elements and the delimiter
+ operationId = String.join(removeOperationIdPrefixDelimiter, Arrays.copyOfRange(components, component_number, components.length));
+ }
+ }
+ operationId = removeNonNameElementToCamelCase(operationId);
+
+ if (operationIdNameMapping.containsKey(operationId)) {
+ return operationIdNameMapping.get(operationId);
+ } else {
+ return toOperationId(operationId);
+ }
+ }
+
+ /**
+ * Check the type to see if it needs import the library/module/package
+ *
+ * @param type name of the type
+ * @return true if the library/module/package of the corresponding type needs to be imported
+ */
+ protected boolean needToImport(String type) {
+ return StringUtils.isNotBlank(type) && !defaultIncludes.contains(type)
+ && !languageSpecificPrimitives.contains(type);
+ }
+
+ @SuppressWarnings("static-method")
+ protected List> toExamples(Map examples) {
+ if (examples == null) {
+ return null;
+ }
+
+ final List> output = new ArrayList<>(examples.size());
+ for (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