Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,15 @@
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.stream.Collectors;
import io.swagger.v3.oas.models.media.ComposedSchema;
import io.swagger.v3.oas.models.media.Content;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.parameters.RequestBody;
import io.swagger.v3.oas.models.responses.ApiResponse;
import org.apache.commons.lang3.tuple.Pair;
import org.openapitools.codegen.CliOption;
import org.openapitools.codegen.CodegenConstants;
Expand Down Expand Up @@ -64,6 +71,7 @@
import org.openapitools.codegen.model.ModelsMap;
import org.openapitools.codegen.templating.mustache.SplitStringLambda;
import org.openapitools.codegen.templating.mustache.TrimWhitespaceLambda;
import org.openapitools.codegen.utils.ModelUtils;
import org.openapitools.codegen.utils.URLPathUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -140,6 +148,8 @@ public SpringCodegen() {
modelPackage = "org.openapitools.model";
invokerPackage = "org.openapitools.api";
artifactId = "openapi-spring";
useOneOfInterfaces = true;
addOneOfInterfaceImports = true;

// clioOptions default redefinition need to be updated
updateOption(CodegenConstants.INVOKER_PACKAGE, this.getInvokerPackage());
Expand Down Expand Up @@ -569,7 +579,11 @@ public void addOperationToGroup(String tag, String resourcePath, Operation opera

@Override
public void preprocessOpenAPI(OpenAPI openAPI) {
super.preprocessOpenAPI(openAPI);
if (openAPI.getComponents() != null) {
preprocessInlineOneOf(openAPI);
super.preprocessOpenAPI(openAPI);
}

/*
* TODO the following logic should not need anymore in OAS 3.0 if
* ("/".equals(swagger.getBasePath())) { swagger.setBasePath(""); }
Expand Down Expand Up @@ -953,6 +967,99 @@ public void setUseOptional(boolean useOptional) {
}

@Override
public void addImportsToOneOfInterface(List<Map<String, String>> imports) {
if (additionalProperties.containsKey(JACKSON)) {
for (String i : Arrays.asList("JsonSubTypes", "JsonTypeInfo")) {
Map<String, String> oneImport = new HashMap<>();
oneImport.put("import", importMapping.get(i));
if (!imports.contains(oneImport)) {
imports.add(oneImport);
}
}
}
}

@Override
public Map<String, ModelsMap> postProcessAllModels(Map<String, ModelsMap> objs) {
Map<String, ModelsMap> postProcessedModels = super.postProcessAllModels(objs);

for (Map.Entry<String, ModelsMap> modelsEntry : objs.entrySet()) {
ModelsMap modelsMap = modelsEntry.getValue();
List<ModelMap> models = modelsMap.getModels();
for (ModelMap modelMap : models) {
CodegenModel cm = (CodegenModel) modelMap.get("model");
if (cm.oneOf.size() > 0) {
cm.vendorExtensions.put("x-deduction", true);
cm.vendorExtensions.put("x-deduction-model-names", cm.oneOf.toArray());
}
}
}

return postProcessedModels;
}

private void preprocessInlineOneOf(OpenAPI openAPI) {
if (openAPI != null) {
if (openAPI.getPaths() != null) {
for (Map.Entry<String, PathItem> openAPIGetPathsEntry : openAPI.getPaths().entrySet()) {
String pathname = openAPIGetPathsEntry.getKey();
PathItem path = openAPIGetPathsEntry.getValue();
if (path.readOperations() == null) {
continue;
}
for (Operation operation : path.readOperations()) {
boolean hasBodyParameter = hasBodyParameter(openAPI, operation);

// OpenAPI parser do not add Inline One Of models in Operations to Components/Schemas
if (hasBodyParameter(openAPI, operation)) {
Optional.ofNullable(operation.getRequestBody())
.map(RequestBody::getContent)
.ifPresent(this::repairInlineOneOf);
}
if (operation.getResponses() != null) {
operation.getResponses().values().stream().map(ApiResponse::getContent)
.filter(Objects::nonNull)
.forEach(this::repairInlineOneOf);
}
}
}
}
}
}

/**
* Add all OneOf schemas to #/components/schemas and replace them in the original content by ref schema
* Replace OneOf with unmodifiable types with an empty Schema
*
* OpenAPI Parser does not add inline OneOf schemas to models to generate
*
* @param content a 'content' section in the OAS specification.
*/
private void repairInlineOneOf(final Content content) {
content.values().forEach(mediaType -> {
final Schema<?> replacingSchema = mediaType.getSchema();
if (isOneOfSchema(replacingSchema)) {
if (ModelUtils.isSchemaOneOfConsistsOfCustomTypes(openAPI, replacingSchema)) {
final String oneOfModelName = (String) replacingSchema.getExtensions().get("x-one-of-name");
final Schema<?> newRefSchema = new Schema<>().$ref("#/components/schemas/" + oneOfModelName);
mediaType.setSchema(newRefSchema);
ModelUtils.getSchemas(openAPI).put(oneOfModelName, replacingSchema);
} else {
mediaType.setSchema(new Schema().type("object"));
}
}
});
}

private static boolean isOneOfSchema(final Schema<?> schema) {
if (schema instanceof ComposedSchema) {
ComposedSchema composedSchema = (ComposedSchema) schema;
return Optional.ofNullable(composedSchema.getProperties()).map(Map::isEmpty).orElse(true)
&& Optional.ofNullable(schema.getExtensions()).map(m -> m.containsKey("x-one-of-name")).orElse(false);
}
return false;
}

public void setUseSwaggerUI(boolean useSwaggerUI) {
this.useSwaggerUI = useSwaggerUI;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1673,4 +1673,25 @@ public static SemVer getOpenApiVersion(OpenAPI openAPI, String location, List<Au

return new SemVer(version);
}

public static boolean isSchemaOneOfConsistsOfCustomTypes(OpenAPI openAPI, Schema<?> schema) {
if (schema instanceof ComposedSchema) {
ComposedSchema composedSchema = (ComposedSchema) schema;
if (composedSchema.getOneOf() == null || composedSchema.getOneOf().isEmpty()) {
return false;
}
for (Schema<?> oneOfSchema : composedSchema.getOneOf()) {
if (oneOfSchema.get$ref() != null) {
oneOfSchema = ModelUtils.getReferencedSchema(openAPI, schema);
}
if (!(oneOfSchema instanceof ComposedSchema
|| oneOfSchema instanceof MapSchema
|| oneOfSchema instanceof ArraySchema
|| oneOfSchema instanceof ObjectSchema)) {
return false;
}
}
}
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
{{/discriminator}}
{{>generatedAnnotation}}
public interface {{classname}}{{#vendorExtensions.x-implements}}{{#-first}} extends {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} {
{{#discriminator}}
{{^vendorExtensions.x-deduction}}{{#discriminator}}
public {{propertyType}} {{propertyGetter}}();
{{/discriminator}}
{{/discriminator}}{{/vendorExtensions.x-deduction}}
}
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,7 @@ public class {{classname}}{{#parent}} extends {{{parent}}}{{/parent}}{{^parent}}
{{/openApiNullable}}
return this;
}
{{/isArray}}
{{#isMap}}

{{/isArray}}{{#isMap}}{{#items.datatypeWithEnum}}
public {{classname}} put{{nameInCamelCase}}Item(String key, {{{items.datatypeWithEnum}}} {{name}}Item) {
{{^required}}
if (this.{{name}} == null) {
Expand All @@ -121,7 +119,7 @@ public class {{classname}}{{#parent}} extends {{{parent}}}{{/parent}}{{^parent}}
this.{{name}}.put(key, {{name}}Item);
return this;
}
{{/isMap}}
{{/items.datatypeWithEnum}}{{/isMap}}
{{! end feature: fluent setter methods }}
{{! begin feature: getter and setter }}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
{{#jackson}}
{{#discriminator.mappedModels}}
{{#-first}}
{{#vendorExtensions.x-deduction}}
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "{{{discriminator.propertyBaseName}}}", visible = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION, visible = true)
{{/vendorExtensions.x-deduction}}
{{^vendorExtensions.x-deduction}}
@JsonIgnoreProperties(
value = "{{{discriminator.propertyBaseName}}}", // ignore manually set {{{discriminator.propertyBaseName}}}, it will be automatically generated by Jackson during serialization
allowSetters = true // allows the {{{discriminator.propertyBaseName}}} to be set during deserialization
value = "{{{discriminator.propertyBaseName}}}", // ignore manually set {{{discriminator.propertyBaseName}}}, it will be automatically generated by Jackson during serialization
allowSetters = true // allows the {{{discriminator.propertyBaseName}}} to be set during deserialization
)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "{{{discriminator.propertyBaseName}}}", visible = true)
{{/vendorExtensions.x-deduction}}
@JsonSubTypes({
{{/-first}}
{{^vendorExtensions.x-discriminator-value}}
@JsonSubTypes.Type(value = {{modelName}}.class, name = "{{{mappingName}}}"){{^-last}},{{/-last}}
{{/vendorExtensions.x-discriminator-value}}
{{#vendorExtensions.x-discriminator-value}}
@JsonSubTypes.Type(value = {{modelName}}.class, name = "{{{vendorExtensions.x-discriminator-value}}}"){{^-last}},{{/-last}}
{{/vendorExtensions.x-discriminator-value}}
{{^vendorExtensions.x-discriminator-value}}
@JsonSubTypes.Type(value = {{modelName}}.class, name = "{{{mappingName}}}"){{^-last}},{{/-last}}
{{/vendorExtensions.x-discriminator-value}}
{{#vendorExtensions.x-discriminator-value}}
@JsonSubTypes.Type(value = {{modelName}}.class, name = "{{{vendorExtensions.x-discriminator-value}}}"){{^-last}},{{/-last}}
{{/vendorExtensions.x-discriminator-value}}
{{#-last}}
})
{{/-last}}
Expand Down
Loading