forked from loafle/openapi-generator-original
[go-experimental] Add oneOf support (#5150)
* [go-experimental] Add oneOf support * Fix docs for the oneOf models * isOneOfInterface => x-is-one-of-interface * Add proper warnings when inline models are used in oneOf choices * Add a convenience method to oneOf implementing structs to cast them as the oneOf interface * Update modules/openapi-generator/src/main/resources/go-experimental/model.mustache Co-Authored-By: Jiri Kuncar <jiri.kuncar@gmail.com> * Fix retrieving data from additionalDataMap * Add basic tests Co-authored-by: Jiri Kuncar <jiri.kuncar@gmail.com>
This commit is contained in:
parent
cd91a15953
commit
0693a83cfe
@ -26,6 +26,7 @@ import com.samskivert.mustache.Mustache.Lambda;
|
||||
import io.swagger.v3.core.util.Json;
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.Operation;
|
||||
import io.swagger.v3.oas.models.PathItem;
|
||||
import io.swagger.v3.oas.models.callbacks.Callback;
|
||||
import io.swagger.v3.oas.models.examples.Example;
|
||||
import io.swagger.v3.oas.models.headers.Header;
|
||||
@ -59,6 +60,7 @@ import org.openapitools.codegen.templating.mustache.LowercaseLambda;
|
||||
import org.openapitools.codegen.templating.mustache.TitlecaseLambda;
|
||||
import org.openapitools.codegen.templating.mustache.UppercaseLambda;
|
||||
import org.openapitools.codegen.utils.ModelUtils;
|
||||
import org.openapitools.codegen.utils.OneOfImplementorAdditionalData;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -187,6 +189,11 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
// flag to indicate whether to use environment variable to post process file
|
||||
protected boolean enablePostProcessFile = false;
|
||||
private TemplatingEngineAdapter templatingEngine = new MustacheEngineAdapter();
|
||||
// flag to indicate whether to use the utils.OneOfImplementorAdditionalData related logic
|
||||
protected boolean useOneOfInterfaces = false;
|
||||
// whether or not the oneOf imports machinery should add oneOf interfaces as imports in implementing classes
|
||||
protected boolean addOneOfInterfaceImports = false;
|
||||
protected List<CodegenModel> addOneOfInterfaces = new ArrayList<CodegenModel>();
|
||||
|
||||
// flag to indicate whether to only update files whose contents have changed
|
||||
protected boolean enableMinimalUpdate = false;
|
||||
@ -326,6 +333,65 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
// override with any special post-processing for all models
|
||||
@SuppressWarnings({"static-method", "unchecked"})
|
||||
public Map<String, Object> postProcessAllModels(Map<String, Object> objs) {
|
||||
if (this.useOneOfInterfaces) {
|
||||
// First, add newly created oneOf interfaces
|
||||
for (CodegenModel cm : addOneOfInterfaces) {
|
||||
Map<String, Object> modelValue = new HashMap<String, Object>() {{
|
||||
putAll(additionalProperties());
|
||||
put("model", cm);
|
||||
}};
|
||||
List<Object> modelsValue = Arrays.asList(modelValue);
|
||||
List<Map<String, String>> importsValue = new ArrayList<Map<String, String>>();
|
||||
Map<String, Object> objsValue = new HashMap<String, Object>() {{
|
||||
put("models", modelsValue);
|
||||
put("package", modelPackage());
|
||||
put("imports", importsValue);
|
||||
put("classname", cm.classname);
|
||||
putAll(additionalProperties);
|
||||
}};
|
||||
objs.put(cm.name, objsValue);
|
||||
}
|
||||
|
||||
// Gather data from all the models that contain oneOf into OneOfImplementorAdditionalData classes
|
||||
// (see docstring of that class to find out what information is gathered and why)
|
||||
Map<String, OneOfImplementorAdditionalData> additionalDataMap = new HashMap<String, OneOfImplementorAdditionalData>();
|
||||
for (Map.Entry modelsEntry : objs.entrySet()) {
|
||||
Map<String, Object> modelsAttrs = (Map<String, Object>) modelsEntry.getValue();
|
||||
List<Object> models = (List<Object>) modelsAttrs.get("models");
|
||||
List<Map<String, String>> modelsImports = (List<Map<String, String>>) modelsAttrs.getOrDefault("imports", new ArrayList<Map<String, String>>());
|
||||
for (Object _mo : models) {
|
||||
Map<String, Object> mo = (Map<String, Object>) _mo;
|
||||
CodegenModel cm = (CodegenModel) mo.get("model");
|
||||
if (cm.oneOf.size() > 0) {
|
||||
cm.vendorExtensions.put("x-is-one-of-interface", true);
|
||||
for (String one : cm.oneOf) {
|
||||
if (!additionalDataMap.containsKey(one)) {
|
||||
additionalDataMap.put(one, new OneOfImplementorAdditionalData(one));
|
||||
}
|
||||
additionalDataMap.get(one).addFromInterfaceModel(cm, modelsImports);
|
||||
}
|
||||
// if this is oneOf interface, make sure we include the necessary imports for it
|
||||
addImportsToOneOfInterface(modelsImports);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add all the data from OneOfImplementorAdditionalData classes to the implementing models
|
||||
for (Map.Entry modelsEntry : objs.entrySet()) {
|
||||
Map<String, Object> modelsAttrs = (Map<String, Object>) modelsEntry.getValue();
|
||||
List<Object> models = (List<Object>) modelsAttrs.get("models");
|
||||
List<Map<String, String>> imports = (List<Map<String, String>>) modelsAttrs.get("imports");
|
||||
for (Object _implmo : models) {
|
||||
Map<String, Object> implmo = (Map<String, Object>) _implmo;
|
||||
CodegenModel implcm = (CodegenModel) implmo.get("model");
|
||||
String modelName = toModelName(implcm.name);
|
||||
if (additionalDataMap.containsKey(modelName)) {
|
||||
additionalDataMap.get(modelName).addToImplementor(this, implcm, imports, addOneOfInterfaceImports);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return objs;
|
||||
}
|
||||
|
||||
@ -626,6 +692,62 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
//override with any special handling of the entire OpenAPI spec document
|
||||
@SuppressWarnings("unused")
|
||||
public void preprocessOpenAPI(OpenAPI openAPI) {
|
||||
if (useOneOfInterfaces) {
|
||||
// we process the openapi schema here to find oneOf schemas and create interface models for them
|
||||
Map<String, Schema> schemas = new HashMap<String, Schema>(openAPI.getComponents().getSchemas());
|
||||
if (schemas == null) {
|
||||
schemas = new HashMap<String, Schema>();
|
||||
}
|
||||
Map<String, PathItem> pathItems = openAPI.getPaths();
|
||||
|
||||
// we need to add all request and response bodies to processed schemas
|
||||
if (pathItems != null) {
|
||||
for (Map.Entry<String, PathItem> e : pathItems.entrySet()) {
|
||||
for (Map.Entry<PathItem.HttpMethod, Operation> op : e.getValue().readOperationsMap().entrySet()) {
|
||||
String opId = getOrGenerateOperationId(op.getValue(), e.getKey(), op.getKey().toString());
|
||||
// process request body
|
||||
RequestBody b = ModelUtils.getReferencedRequestBody(openAPI, op.getValue().getRequestBody());
|
||||
Schema requestSchema = null;
|
||||
if (b != null) {
|
||||
requestSchema = ModelUtils.getSchemaFromRequestBody(b);
|
||||
}
|
||||
if (requestSchema != null) {
|
||||
schemas.put(opId, requestSchema);
|
||||
}
|
||||
// process all response bodies
|
||||
for (Map.Entry<String, ApiResponse> ar : op.getValue().getResponses().entrySet()) {
|
||||
ApiResponse a = ModelUtils.getReferencedApiResponse(openAPI, ar.getValue());
|
||||
Schema responseSchema = ModelUtils.getSchemaFromResponse(a);
|
||||
if (responseSchema != null) {
|
||||
schemas.put(opId + ar.getKey(), responseSchema);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// go through all gathered schemas and add them as interfaces to be created
|
||||
for (Map.Entry<String, Schema> e : schemas.entrySet()) {
|
||||
String n = toModelName(e.getKey());
|
||||
Schema s = e.getValue();
|
||||
String nOneOf = toModelName(n + "OneOf");
|
||||
if (ModelUtils.isComposedSchema(s)) {
|
||||
addOneOfNameExtension((ComposedSchema) s, n);
|
||||
} else if (ModelUtils.isArraySchema(s)) {
|
||||
Schema items = ((ArraySchema) s).getItems();
|
||||
if (ModelUtils.isComposedSchema(items)) {
|
||||
addOneOfNameExtension((ComposedSchema) items, nOneOf);
|
||||
addOneOfInterfaceModel((ComposedSchema) items, nOneOf);
|
||||
}
|
||||
} else if (ModelUtils.isMapSchema(s)) {
|
||||
Schema addProps = ModelUtils.getAdditionalProperties(s);
|
||||
if (addProps != null && ModelUtils.isComposedSchema(addProps)) {
|
||||
addOneOfNameExtension((ComposedSchema) addProps, nOneOf);
|
||||
addOneOfInterfaceModel((ComposedSchema) addProps, nOneOf);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// override with any special handling of the entire OpenAPI spec document
|
||||
@ -950,6 +1072,12 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
this.allowUnicodeIdentifiers = allowUnicodeIdentifiers;
|
||||
}
|
||||
|
||||
public Boolean getUseOneOfInterfaces() { return useOneOfInterfaces; }
|
||||
|
||||
public void setUseOneOfInterfaces(Boolean useOneOfInterfaces) {
|
||||
this.useOneOfInterfaces = useOneOfInterfaces;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the regular expression/JSON schema pattern (http://json-schema.org/latest/json-schema-validation.html#anchor33)
|
||||
*
|
||||
@ -5534,4 +5662,49 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
public void setRemoveEnumValuePrefix(final boolean removeEnumValuePrefix) {
|
||||
this.removeEnumValuePrefix = removeEnumValuePrefix;
|
||||
}
|
||||
}
|
||||
|
||||
//// Following methods are related to the "useOneOfInterfaces" feature
|
||||
/**
|
||||
* Add "x-oneOf-name" extension to a given oneOf schema (assuming it has at least 1 oneOf elements)
|
||||
* @param s schema to add the extension to
|
||||
* @param name name of the parent oneOf schema
|
||||
*/
|
||||
public void addOneOfNameExtension(ComposedSchema s, String name) {
|
||||
if (s.getOneOf() != null && s.getOneOf().size() > 0) {
|
||||
s.addExtension("x-oneOf-name", name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a given ComposedSchema as an interface model to be generated
|
||||
* @param cs ComposedSchema object to create as interface model
|
||||
* @param type name to use for the generated interface model
|
||||
*/
|
||||
public void addOneOfInterfaceModel(ComposedSchema cs, String type) {
|
||||
CodegenModel cm = new CodegenModel();
|
||||
|
||||
cm.discriminator = createDiscriminator("", (Schema) cs);
|
||||
for (Schema o : cs.getOneOf()) {
|
||||
if (o.get$ref() == null) {
|
||||
if (cm.discriminator != null && o.get$ref() == null) {
|
||||
// OpenAPI spec states that inline objects should not be considered when discriminator is used
|
||||
// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#discriminatorObject
|
||||
LOGGER.warn("Ignoring inline object in oneOf definition of {}, since discriminator is used", type);
|
||||
} else {
|
||||
LOGGER.warn("Inline models are not supported in oneOf definition right now");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
cm.oneOf.add(toModelName(ModelUtils.getSimpleRef(o.get$ref())));
|
||||
}
|
||||
cm.name = type;
|
||||
cm.classname = type;
|
||||
cm.vendorExtensions.put("x-is-one-of-interface", true);
|
||||
cm.interfaceModels = new ArrayList<CodegenModel>();
|
||||
|
||||
addOneOfInterfaces.add(cm);
|
||||
}
|
||||
|
||||
public void addImportsToOneOfInterface(List<Map<String, String>> imports) {}
|
||||
//// End of methods related to the "useOneOfInterfaces" feature
|
||||
}
|
||||
|
@ -522,6 +522,7 @@ public abstract class AbstractJavaCodegen extends DefaultCodegen implements Code
|
||||
|
||||
@Override
|
||||
public Map<String, Object> postProcessAllModels(Map<String, Object> objs) {
|
||||
objs = super.postProcessAllModels(objs);
|
||||
objs = super.updateAllModels(objs);
|
||||
|
||||
if (!additionalModelTypeAnnotations.isEmpty()) {
|
||||
@ -1067,6 +1068,7 @@ public abstract class AbstractJavaCodegen extends DefaultCodegen implements Code
|
||||
|
||||
@Override
|
||||
public void preprocessOpenAPI(OpenAPI openAPI) {
|
||||
super.preprocessOpenAPI(openAPI);
|
||||
if (openAPI == null) {
|
||||
return;
|
||||
}
|
||||
|
@ -27,6 +27,8 @@ import org.openapitools.codegen.utils.ProcessUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@ -42,6 +44,7 @@ public class GoClientExperimentalCodegen extends GoClientCodegen {
|
||||
embeddedTemplateDir = templateDir = "go-experimental";
|
||||
|
||||
usesOptionals = false;
|
||||
useOneOfInterfaces = true;
|
||||
|
||||
generatorMetadata = GeneratorMetadata.newBuilder(generatorMetadata).stability(Stability.EXPERIMENTAL).build();
|
||||
}
|
||||
@ -57,6 +60,11 @@ public class GoClientExperimentalCodegen extends GoClientCodegen {
|
||||
return "go-experimental";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toGetter(String name) {
|
||||
return "Get" + getterAndSetterCapitalize(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns human-friendly help for the generator. Provide the consumer with help
|
||||
* tips, parameters here
|
||||
@ -125,4 +133,16 @@ public class GoClientExperimentalCodegen extends GoClientCodegen {
|
||||
objs = super.postProcessModels(objs);
|
||||
return objs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addImportsToOneOfInterface(List<Map<String, String>> imports) {
|
||||
for (String i : Arrays.asList("fmt")) {
|
||||
Map<String, String> oneImport = new HashMap<String, String>() {{
|
||||
put("import", i);
|
||||
}};
|
||||
if (!imports.contains(oneImport)) {
|
||||
imports.add(oneImport);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,19 +25,12 @@ import org.openapitools.codegen.languages.features.GzipFeatures;
|
||||
import org.openapitools.codegen.languages.features.PerformBeanValidationFeatures;
|
||||
import org.openapitools.codegen.meta.features.DocumentationFeature;
|
||||
import org.openapitools.codegen.templating.mustache.CaseFormatLambda;
|
||||
import org.openapitools.codegen.utils.ModelUtils;
|
||||
import org.openapitools.codegen.utils.ProcessUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.swagger.v3.oas.models.Operation;
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.PathItem;
|
||||
import io.swagger.v3.oas.models.media.ArraySchema;
|
||||
import io.swagger.v3.oas.models.media.ComposedSchema;
|
||||
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 java.io.File;
|
||||
import java.util.*;
|
||||
@ -111,9 +104,6 @@ public class JavaClientCodegen extends AbstractJavaCodegen
|
||||
protected String authFolder;
|
||||
protected String serializationLibrary = null;
|
||||
|
||||
protected boolean useOneOfInterfaces = false;
|
||||
protected List<CodegenModel> addOneOfInterfaces = new ArrayList<CodegenModel>();
|
||||
|
||||
public JavaClientCodegen() {
|
||||
super();
|
||||
|
||||
@ -501,6 +491,7 @@ public class JavaClientCodegen extends AbstractJavaCodegen
|
||||
|
||||
if (additionalProperties.containsKey(SERIALIZATION_LIBRARY_JACKSON)) {
|
||||
useOneOfInterfaces = true;
|
||||
addOneOfInterfaceImports = true;
|
||||
}
|
||||
|
||||
}
|
||||
@ -846,259 +837,6 @@ public class JavaClientCodegen extends AbstractJavaCodegen
|
||||
}
|
||||
}
|
||||
|
||||
public void addOneOfNameExtension(Schema s, String name) {
|
||||
ComposedSchema cs = (ComposedSchema) s;
|
||||
if (cs.getOneOf() != null && cs.getOneOf().size() > 0) {
|
||||
cs.addExtension("x-oneOf-name", name);
|
||||
}
|
||||
}
|
||||
|
||||
public void addOneOfInterfaceModel(ComposedSchema cs, String type) {
|
||||
CodegenModel cm = new CodegenModel();
|
||||
|
||||
// TODO: 5.0: Remove the camelCased vendorExtension below and ensure templates use the newer property naming.
|
||||
once(LOGGER).warn("4.3.0 has deprecated the use of vendor extensions which don't follow lower-kebab casing standards with x- prefix.");
|
||||
|
||||
|
||||
for (Schema o : cs.getOneOf()) {
|
||||
// TODO: inline objects
|
||||
cm.oneOf.add(toModelName(ModelUtils.getSimpleRef(o.get$ref())));
|
||||
}
|
||||
cm.name = type;
|
||||
cm.classname = type;
|
||||
cm.vendorExtensions.put("isOneOfInterface", true); // TODO: 5.0 Remove
|
||||
cm.vendorExtensions.put("x-is-one-of-interface", true);
|
||||
cm.discriminator = createDiscriminator("", (Schema) cs);
|
||||
cm.interfaceModels = new ArrayList<CodegenModel>();
|
||||
|
||||
addOneOfInterfaces.add(cm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preprocessOpenAPI(OpenAPI openAPI) {
|
||||
// we process the openapi schema here to find oneOf schemas here and create interface models for them
|
||||
super.preprocessOpenAPI(openAPI);
|
||||
Map<String, Schema> schemas = new HashMap<String, Schema>(openAPI.getComponents().getSchemas());
|
||||
if (schemas == null) {
|
||||
schemas = new HashMap<String, Schema>();
|
||||
}
|
||||
Map<String, PathItem> pathItems = openAPI.getPaths();
|
||||
|
||||
// we need to add all request and response bodies to processed schemas
|
||||
if (pathItems != null) {
|
||||
for (Map.Entry<String, PathItem> e : pathItems.entrySet()) {
|
||||
for (Map.Entry<PathItem.HttpMethod, Operation> op : e.getValue().readOperationsMap().entrySet()) {
|
||||
String opId = getOrGenerateOperationId(op.getValue(), e.getKey(), op.getKey().toString());
|
||||
// process request body
|
||||
RequestBody b = ModelUtils.getReferencedRequestBody(openAPI, op.getValue().getRequestBody());
|
||||
Schema requestSchema = null;
|
||||
if (b != null) {
|
||||
requestSchema = ModelUtils.getSchemaFromRequestBody(b);
|
||||
}
|
||||
if (requestSchema != null) {
|
||||
schemas.put(opId, requestSchema);
|
||||
}
|
||||
// process all response bodies
|
||||
for (Map.Entry<String, ApiResponse> ar : op.getValue().getResponses().entrySet()) {
|
||||
ApiResponse a = ModelUtils.getReferencedApiResponse(openAPI, ar.getValue());
|
||||
Schema responseSchema = ModelUtils.getSchemaFromResponse(a);
|
||||
if (responseSchema != null) {
|
||||
schemas.put(opId + ar.getKey(), responseSchema);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<String, Schema> e : schemas.entrySet()) {
|
||||
String n = toModelName(e.getKey());
|
||||
Schema s = e.getValue();
|
||||
String nOneOf = toModelName(n + "OneOf");
|
||||
if (ModelUtils.isComposedSchema(s)) {
|
||||
addOneOfNameExtension(s, n);
|
||||
} else if (ModelUtils.isArraySchema(s)) {
|
||||
Schema items = ((ArraySchema) s).getItems();
|
||||
if (ModelUtils.isComposedSchema(items)) {
|
||||
addOneOfNameExtension(items, nOneOf);
|
||||
addOneOfInterfaceModel((ComposedSchema) items, nOneOf);
|
||||
}
|
||||
} else if (ModelUtils.isMapSchema(s)) {
|
||||
Schema addProps = ModelUtils.getAdditionalProperties(s);
|
||||
if (addProps != null && ModelUtils.isComposedSchema(addProps)) {
|
||||
addOneOfNameExtension(addProps, nOneOf);
|
||||
addOneOfInterfaceModel((ComposedSchema) addProps, nOneOf);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class OneOfImplementorAdditionalData {
|
||||
private String implementorName;
|
||||
private List<String> additionalInterfaces = new ArrayList<String>();
|
||||
private List<CodegenProperty> additionalProps = new ArrayList<CodegenProperty>();
|
||||
private List<Map<String, String>> additionalImports = new ArrayList<Map<String, String>>();
|
||||
|
||||
public OneOfImplementorAdditionalData(String implementorName) {
|
||||
this.implementorName = implementorName;
|
||||
}
|
||||
|
||||
public String getImplementorName() {
|
||||
return implementorName;
|
||||
}
|
||||
|
||||
public void addFromInterfaceModel(CodegenModel cm, List<Map<String, String>> modelsImports) {
|
||||
// Add cm as implemented interface
|
||||
additionalInterfaces.add(cm.classname);
|
||||
|
||||
// Add all vars defined on cm
|
||||
// a "oneOf" model (cm) by default inherits all properties from its "interfaceModels",
|
||||
// but we only want to add properties defined on cm itself
|
||||
List<CodegenProperty> toAdd = new ArrayList<CodegenProperty>(cm.vars);
|
||||
// note that we can't just toAdd.removeAll(m.vars) for every interfaceModel,
|
||||
// as they might have different value of `hasMore` and thus are not equal
|
||||
List<String> omitAdding = new ArrayList<String>();
|
||||
for (CodegenModel m : cm.interfaceModels) {
|
||||
for (CodegenProperty v : m.vars) {
|
||||
omitAdding.add(v.baseName);
|
||||
}
|
||||
}
|
||||
for (CodegenProperty v : toAdd) {
|
||||
if (!omitAdding.contains(v.baseName)) {
|
||||
additionalProps.add(v.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// Add all imports of cm
|
||||
for (Map<String, String> importMap : modelsImports) {
|
||||
// we're ok with shallow clone here, because imports are strings only
|
||||
additionalImports.add(new HashMap<String, String>(importMap));
|
||||
}
|
||||
}
|
||||
|
||||
public void addToImplementor(CodegenModel implcm, List<Map<String, String>> implImports) {
|
||||
implcm.getVendorExtensions().putIfAbsent("implements", new ArrayList<String>());
|
||||
|
||||
// Add implemented interfaces
|
||||
for (String intf : additionalInterfaces) {
|
||||
List<String> impl = (List<String>) implcm.getVendorExtensions().get("implements");
|
||||
impl.add(intf);
|
||||
// Add imports for interfaces
|
||||
implcm.imports.add(intf);
|
||||
Map<String, String> importsItem = new HashMap<String, String>();
|
||||
importsItem.put("import", toModelImport(intf));
|
||||
implImports.add(importsItem);
|
||||
}
|
||||
|
||||
// Add oneOf-containing models properties - we need to properly set the hasMore values to make renderind correct
|
||||
if (implcm.vars.size() > 0 && additionalProps.size() > 0) {
|
||||
implcm.vars.get(implcm.vars.size() - 1).hasMore = true;
|
||||
}
|
||||
for (int i = 0; i < additionalProps.size(); i++) {
|
||||
CodegenProperty var = additionalProps.get(i);
|
||||
if (i == additionalProps.size() - 1) {
|
||||
var.hasMore = false;
|
||||
} else {
|
||||
var.hasMore = true;
|
||||
}
|
||||
implcm.vars.add(var);
|
||||
}
|
||||
|
||||
// Add imports
|
||||
for (Map<String, String> oneImport : additionalImports) {
|
||||
// exclude imports from this package - these are imports that only the oneOf interface needs
|
||||
if (!implImports.contains(oneImport) && !oneImport.getOrDefault("import", "").startsWith(modelPackage())) {
|
||||
implImports.add(oneImport);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> postProcessAllModels(Map<String, Object> objs) {
|
||||
objs = super.postProcessAllModels(objs);
|
||||
|
||||
// TODO: 5.0: Remove the camelCased vendorExtension below and ensure templates use the newer property naming.
|
||||
once(LOGGER).warn("4.3.0 has deprecated the use of vendor extensions which don't follow lower-kebab casing standards with x- prefix.");
|
||||
|
||||
if (this.useOneOfInterfaces) {
|
||||
// First, add newly created oneOf interfaces
|
||||
for (CodegenModel cm : addOneOfInterfaces) {
|
||||
Map<String, Object> modelValue = new HashMap<String, Object>() {{
|
||||
putAll(additionalProperties());
|
||||
put("model", cm);
|
||||
}};
|
||||
List<Object> modelsValue = Arrays.asList(modelValue);
|
||||
List<Map<String, String>> importsValue = new ArrayList<Map<String, String>>();
|
||||
for (String i : Arrays.asList("JsonSubTypes", "JsonTypeInfo")) {
|
||||
Map<String, String> oneImport = new HashMap<String, String>() {{
|
||||
put("import", importMapping.get(i));
|
||||
}};
|
||||
importsValue.add(oneImport);
|
||||
}
|
||||
Map<String, Object> objsValue = new HashMap<String, Object>() {{
|
||||
put("models", modelsValue);
|
||||
put("package", modelPackage());
|
||||
put("imports", importsValue);
|
||||
put("classname", cm.classname);
|
||||
putAll(additionalProperties);
|
||||
}};
|
||||
objs.put(cm.name, objsValue);
|
||||
}
|
||||
|
||||
// - Add all "oneOf" models as interfaces to be implemented by the models that
|
||||
// are the choices in "oneOf"; also mark the models containing "oneOf" as interfaces
|
||||
// - Add all properties of "oneOf" to the implementing classes (NOTE that this
|
||||
// would be problematic if the class was in multiple such "oneOf" models, in which
|
||||
// case it would get all their properties, but it's probably better than not doing this)
|
||||
// - Add all imports of "oneOf" model to all the implementing classes (this might not
|
||||
// be optimal, as it can contain more than necessary, but it's good enough)
|
||||
Map<String, OneOfImplementorAdditionalData> additionalDataMap = new HashMap<String, OneOfImplementorAdditionalData>();
|
||||
for (Map.Entry modelsEntry : objs.entrySet()) {
|
||||
Map<String, Object> modelsAttrs = (Map<String, Object>) modelsEntry.getValue();
|
||||
List<Object> models = (List<Object>) modelsAttrs.get("models");
|
||||
List<Map<String, String>> modelsImports = (List<Map<String, String>>) modelsAttrs.getOrDefault("imports", new ArrayList<Map<String, String>>());
|
||||
for (Object _mo : models) {
|
||||
Map<String, Object> mo = (Map<String, Object>) _mo;
|
||||
CodegenModel cm = (CodegenModel) mo.get("model");
|
||||
if (cm.oneOf.size() > 0) {
|
||||
cm.vendorExtensions.put("isOneOfInterface", true); // TODO: 5.0 Remove
|
||||
cm.vendorExtensions.put("x-is-one-of-interface", true);
|
||||
// if this is oneOf interface, make sure we include the necessary jackson imports for it
|
||||
for (String s : Arrays.asList("JsonTypeInfo", "JsonSubTypes")) {
|
||||
Map<String, String> i = new HashMap<String, String>() {{
|
||||
put("import", importMapping.get(s));
|
||||
}};
|
||||
if (!modelsImports.contains(i)) {
|
||||
modelsImports.add(i);
|
||||
}
|
||||
}
|
||||
for (String one : cm.oneOf) {
|
||||
if (!additionalDataMap.containsKey(one)) {
|
||||
additionalDataMap.put(one, new OneOfImplementorAdditionalData(one));
|
||||
}
|
||||
additionalDataMap.get(one).addFromInterfaceModel(cm, modelsImports);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry modelsEntry : objs.entrySet()) {
|
||||
Map<String, Object> modelsAttrs = (Map<String, Object>) modelsEntry.getValue();
|
||||
List<Object> models = (List<Object>) modelsAttrs.get("models");
|
||||
List<Map<String, String>> imports = (List<Map<String, String>>) modelsAttrs.get("imports");
|
||||
for (Object _implmo : models) {
|
||||
Map<String, Object> implmo = (Map<String, Object>) _implmo;
|
||||
CodegenModel implcm = (CodegenModel) implmo.get("model");
|
||||
if (additionalDataMap.containsKey(implcm.name)) {
|
||||
additionalDataMap.get(implcm.name).addToImplementor(implcm, imports);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return objs;
|
||||
}
|
||||
|
||||
public void forceSerializationLibrary(String serializationLibrary) {
|
||||
if((this.serializationLibrary != null) && !this.serializationLibrary.equalsIgnoreCase(serializationLibrary)) {
|
||||
LOGGER.warn("The configured serializationLibrary '" + this.serializationLibrary + "', is not supported by the library: '" + getLibrary() + "', switching back to: " + serializationLibrary);
|
||||
@ -1138,4 +876,16 @@ public class JavaClientCodegen extends AbstractJavaCodegen
|
||||
}
|
||||
return apiVarName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addImportsToOneOfInterface(List<Map<String, String>> imports) {
|
||||
for (String i : Arrays.asList("JsonSubTypes", "JsonTypeInfo")) {
|
||||
Map<String, String> oneImport = new HashMap<String, String>() {{
|
||||
put("import", importMapping.get(i));
|
||||
}};
|
||||
if (!imports.contains(oneImport)) {
|
||||
imports.add(oneImport);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,147 @@
|
||||
package org.openapitools.codegen.utils;
|
||||
|
||||
import org.openapitools.codegen.CodegenConfig;
|
||||
import org.openapitools.codegen.CodegenModel;
|
||||
import org.openapitools.codegen.CodegenProperty;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* This class holds data to add to `oneOf` members. Let's consider this example:
|
||||
*
|
||||
* Foo:
|
||||
* properties:
|
||||
* x:
|
||||
* oneOf:
|
||||
* - $ref: "#/components/schemas/One
|
||||
* - $ref: "#/components/schemas/Two
|
||||
* y:
|
||||
* type: string
|
||||
* One:
|
||||
* properties:
|
||||
* z:
|
||||
* type: string
|
||||
* Two:
|
||||
* properties:
|
||||
* a:
|
||||
* type: string
|
||||
*
|
||||
* In codegens that use this mechanism, `Foo` will become an interface and `One` will
|
||||
* become its implementing class. This class carries all data necessary to properly modify
|
||||
* the implementing class model. Specifically:
|
||||
*
|
||||
* * Interfaces that the implementing classes have to implement (in the example above, `One` and `Two` will implement `Foo`)
|
||||
* * Properties that need to be added to implementing classes (as `Foo` is interface, the `y` property will get pushed
|
||||
* to implementing classes `One` and `Two`)
|
||||
* * Imports that need to be added to implementing classes (e.g. if type of property `y` needs a specific import, it
|
||||
* needs to be added to `One` and `Two` because of the above point)
|
||||
*/
|
||||
public class OneOfImplementorAdditionalData {
|
||||
private String implementorName;
|
||||
private List<String> additionalInterfaces = new ArrayList<String>();
|
||||
private List<CodegenProperty> additionalProps = new ArrayList<CodegenProperty>();
|
||||
private List<Map<String, String>> additionalImports = new ArrayList<Map<String, String>>();
|
||||
|
||||
public OneOfImplementorAdditionalData(String implementorName) {
|
||||
this.implementorName = implementorName;
|
||||
}
|
||||
|
||||
public String getImplementorName() {
|
||||
return implementorName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add data from a given CodegenModel that the oneOf implementor should implement. For example:
|
||||
*
|
||||
* @param cm model that the implementor should implement
|
||||
* @param modelsImports imports of the given `cm`
|
||||
*/
|
||||
public void addFromInterfaceModel(CodegenModel cm, List<Map<String, String>> modelsImports) {
|
||||
// Add cm as implemented interface
|
||||
additionalInterfaces.add(cm.classname);
|
||||
|
||||
// Add all vars defined on cm
|
||||
// a "oneOf" model (cm) by default inherits all properties from its "interfaceModels",
|
||||
// but we only want to add properties defined on cm itself
|
||||
List<CodegenProperty> toAdd = new ArrayList<CodegenProperty>(cm.vars);
|
||||
// note that we can't just toAdd.removeAll(m.vars) for every interfaceModel,
|
||||
// as they might have different value of `hasMore` and thus are not equal
|
||||
List<String> omitAdding = new ArrayList<String>();
|
||||
for (CodegenModel m : cm.interfaceModels) {
|
||||
for (CodegenProperty v : m.vars) {
|
||||
omitAdding.add(v.baseName);
|
||||
}
|
||||
}
|
||||
for (CodegenProperty v : toAdd) {
|
||||
if (!omitAdding.contains(v.baseName)) {
|
||||
additionalProps.add(v.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// Add all imports of cm
|
||||
for (Map<String, String> importMap : modelsImports) {
|
||||
// we're ok with shallow clone here, because imports are strings only
|
||||
additionalImports.add(new HashMap<String, String>(importMap));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds stored data to given implementing model
|
||||
*
|
||||
* @param cc CodegenConfig running this operation
|
||||
* @param implcm the implementing model
|
||||
* @param implImports imports of the implementing model
|
||||
* @param addInterfaceImports whether or not to add the interface model as import (will vary by language)
|
||||
*/
|
||||
public void addToImplementor(CodegenConfig cc, CodegenModel implcm, List<Map<String, String>> implImports, boolean addInterfaceImports) {
|
||||
implcm.getVendorExtensions().putIfAbsent("implements", new ArrayList<String>());
|
||||
|
||||
// Add implemented interfaces
|
||||
for (String intf : additionalInterfaces) {
|
||||
List<String> impl = (List<String>) implcm.getVendorExtensions().get("implements");
|
||||
impl.add(intf);
|
||||
if (addInterfaceImports) {
|
||||
// Add imports for interfaces
|
||||
implcm.imports.add(intf);
|
||||
Map<String, String> importsItem = new HashMap<String, String>();
|
||||
importsItem.put("import", cc.toModelImport(intf));
|
||||
implImports.add(importsItem);
|
||||
}
|
||||
}
|
||||
|
||||
// Add oneOf-containing models properties - we need to properly set the hasMore values to make rendering correct
|
||||
if (implcm.vars.size() > 0 && additionalProps.size() > 0) {
|
||||
implcm.vars.get(implcm.vars.size() - 1).hasMore = true;
|
||||
}
|
||||
for (int i = 0; i < additionalProps.size(); i++) {
|
||||
CodegenProperty var = additionalProps.get(i);
|
||||
if (i == additionalProps.size() - 1) {
|
||||
var.hasMore = false;
|
||||
} else {
|
||||
var.hasMore = true;
|
||||
}
|
||||
implcm.vars.add(var);
|
||||
}
|
||||
|
||||
// Add imports
|
||||
for (Map<String, String> oneImport : additionalImports) {
|
||||
// exclude imports from this package - these are imports that only the oneOf interface needs
|
||||
if (!implImports.contains(oneImport) && !oneImport.getOrDefault("import", "").startsWith(cc.modelPackage())) {
|
||||
implImports.add(oneImport);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "OneOfImplementorAdditionalData{" +
|
||||
"implementorName='" + implementorName + '\'' +
|
||||
", additionalInterfaces=" + additionalInterfaces +
|
||||
", additionalProps=" + additionalProps +
|
||||
", additionalImports=" + additionalImports +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -42,6 +42,6 @@ import org.hibernate.validator.constraints.*;
|
||||
|
||||
{{#models}}
|
||||
{{#model}}
|
||||
{{#isEnum}}{{>modelEnum}}{{/isEnum}}{{^isEnum}}{{#vendorExtensions.isOneOfInterface}}{{>oneof_interface}}{{/vendorExtensions.isOneOfInterface}}{{^vendorExtensions.isOneOfInterface}}{{>pojo}}{{/vendorExtensions.isOneOfInterface}}{{/isEnum}}
|
||||
{{#isEnum}}{{>modelEnum}}{{/isEnum}}{{^isEnum}}{{#vendorExtensions.x-is-one-of-interface}}{{>oneof_interface}}{{/vendorExtensions.x-is-one-of-interface}}{{^vendorExtensions.x-is-one-of-interface}}{{>pojo}}{{/vendorExtensions.x-is-one-of-interface}}{{/isEnum}}
|
||||
{{/model}}
|
||||
{{/models}}
|
||||
|
@ -21,7 +21,7 @@ import java.util.Map;
|
||||
public class {{classname}}Test {
|
||||
{{#models}}
|
||||
{{#model}}
|
||||
{{^vendorExtensions.isOneOfInterface}}
|
||||
{{^vendorExtensions.x-is-one-of-interface}}
|
||||
{{^isEnum}}
|
||||
private final {{classname}} model = new {{classname}}();
|
||||
|
||||
@ -44,7 +44,7 @@ public class {{classname}}Test {
|
||||
}
|
||||
|
||||
{{/allVars}}
|
||||
{{/vendorExtensions.isOneOfInterface}}
|
||||
{{/vendorExtensions.x-is-one-of-interface}}
|
||||
{{/model}}
|
||||
{{/models}}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
# {{#vendorExtensions.isOneOfInterface}}Interface {{/vendorExtensions.isOneOfInterface}}{{classname}}
|
||||
# {{#vendorExtensions.x-is-one-of-interface}}Interface {{/vendorExtensions.x-is-one-of-interface}}{{classname}}
|
||||
|
||||
{{#description}}{{&description}}
|
||||
{{/description}}
|
||||
{{^vendorExtensions.isOneOfInterface}}
|
||||
{{^vendorExtensions.x-is-one-of-interface}}
|
||||
## Properties
|
||||
|
||||
Name | Type | Description | Notes
|
||||
@ -26,11 +26,11 @@ Name | Value
|
||||
* {{{.}}}
|
||||
{{/vendorExtensions.implements}}
|
||||
{{/vendorExtensions.implements.0}}
|
||||
{{/vendorExtensions.isOneOfInterface}}
|
||||
{{#vendorExtensions.isOneOfInterface}}
|
||||
{{/vendorExtensions.x-is-one-of-interface}}
|
||||
{{#vendorExtensions.x-is-one-of-interface}}
|
||||
## Implementing Classes
|
||||
|
||||
{{#oneOf}}
|
||||
* {{{.}}}
|
||||
{{/oneOf}}
|
||||
{{/vendorExtensions.isOneOfInterface}}
|
||||
{{/vendorExtensions.x-is-one-of-interface}}
|
@ -29,6 +29,10 @@ const (
|
||||
{{^isEnum}}
|
||||
// {{classname}}{{#description}} {{{description}}}{{/description}}{{^description}} struct for {{{classname}}}{{/description}}
|
||||
type {{classname}} struct {
|
||||
{{#vendorExtensions.x-is-one-of-interface}}
|
||||
{{classname}}Interface interface { {{#discriminator}}{{propertyGetter}}() {{propertyType}}{{/discriminator}} }
|
||||
{{/vendorExtensions.x-is-one-of-interface}}
|
||||
{{^vendorExtensions.x-is-one-of-interface}}
|
||||
{{#parent}}
|
||||
{{^isMapModel}}
|
||||
{{{parent}}}
|
||||
@ -42,10 +46,12 @@ type {{classname}} struct {
|
||||
{{/description}}
|
||||
{{name}} {{^required}}*{{/required}}{{{dataType}}} `json:"{{baseName}}{{^required}},omitempty{{/required}}"{{#withXml}} xml:"{{baseName}}{{#isXmlAttribute}},attr{{/isXmlAttribute}}"{{/withXml}}{{#vendorExtensions.x-go-custom-tag}} {{{.}}}{{/vendorExtensions.x-go-custom-tag}}`
|
||||
{{/vars}}
|
||||
{{/vendorExtensions.x-is-one-of-interface}}
|
||||
}
|
||||
{{/isEnum}}
|
||||
|
||||
{{^isEnum}}
|
||||
{{^vendorExtensions.x-is-one-of-interface}}
|
||||
{{#vars}}
|
||||
{{#required}}
|
||||
// Get{{name}} returns the {{name}} field value
|
||||
@ -100,6 +106,58 @@ func (o *{{classname}}) Set{{name}}(v {{dataType}}) {
|
||||
|
||||
{{/required}}
|
||||
{{/vars}}
|
||||
{{/vendorExtensions.x-is-one-of-interface}}
|
||||
{{#vendorExtensions.x-is-one-of-interface}}
|
||||
func (s *{{classname}}) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(s.{{classname}}Interface)
|
||||
}
|
||||
|
||||
func (s *{{classname}}) UnmarshalJSON(src []byte) error {
|
||||
var err error
|
||||
{{#discriminator}}
|
||||
var unmarshaled map[string]interface{}
|
||||
err = json.Unmarshal(src, &unmarshaled)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if v, ok := unmarshaled["{{discriminator.propertyBaseName}}"]; ok {
|
||||
switch v {
|
||||
{{#discriminator.mappedModels}}
|
||||
case "{{mappingName}}":
|
||||
var result *{{modelName}} = &{{modelName}}{}
|
||||
err = json.Unmarshal(src, result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.{{classname}}Interface = result
|
||||
return nil
|
||||
{{/discriminator.mappedModels}}
|
||||
default:
|
||||
return fmt.Errorf("No oneOf model has '{{discriminator.propertyBaseName}}' equal to %s", v)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("Discriminator property '{{discriminator.propertyBaseName}}' not found in unmarshaled payload: %+v", unmarshaled)
|
||||
}
|
||||
{{/discriminator}}
|
||||
{{^discriminator}}
|
||||
{{#oneOf}}
|
||||
var unmarshaled{{{.}}} *{{{.}}} = &{{{.}}}{}
|
||||
err = json.Unmarshal(src, unmarshaled{{{.}}})
|
||||
if err == nil {
|
||||
s.{{classname}}Interface = unmarshaled{{{.}}}
|
||||
return nil
|
||||
}
|
||||
{{/oneOf}}
|
||||
return fmt.Errorf("No oneOf model could be deserialized from payload: %s", string(src))
|
||||
{{/discriminator}}
|
||||
}
|
||||
{{/vendorExtensions.x-is-one-of-interface}}
|
||||
{{#vendorExtensions.implements}}
|
||||
// As{{{.}}} wraps this instance of {{classname}} in {{{.}}}
|
||||
func (s *{{classname}}) As{{{.}}}() {{{.}}} {
|
||||
return {{{.}}}{ {{{.}}}Interface: s }
|
||||
}
|
||||
{{/vendorExtensions.implements}}
|
||||
{{/isEnum}}
|
||||
type Nullable{{{classname}}} struct {
|
||||
Value {{{classname}}}
|
||||
|
@ -4,12 +4,18 @@
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
{{#vendorExtensions.x-is-one-of-interface}}
|
||||
**{{classname}}Interface** | **interface { {{#discriminator}}{{propertyGetter}}() {{propertyType}}{{/discriminator}} }** | An interface that can hold any of the proper implementing types |
|
||||
{{/vendorExtensions.x-is-one-of-interface}}
|
||||
{{^vendorExtensions.x-is-one-of-interface}}
|
||||
{{#vars}}**{{name}}** | Pointer to {{#isPrimitiveType}}**{{{dataType}}}**{{/isPrimitiveType}}{{^isPrimitiveType}}[**{{{dataType}}}**]({{complexType}}.md){{/isPrimitiveType}} | {{description}} | {{^required}}[optional] {{/required}}{{#isReadOnly}}[readonly] {{/isReadOnly}}{{#defaultValue}}[default to {{{.}}}]{{/defaultValue}}
|
||||
{{/vars}}
|
||||
{{/vendorExtensions.x-is-one-of-interface}}
|
||||
|
||||
{{^isEnum}}
|
||||
## Methods
|
||||
|
||||
{{^vendorExtensions.x-is-one-of-interface}}
|
||||
{{#vars}}
|
||||
### Get{{name}}
|
||||
|
||||
@ -46,6 +52,15 @@ when serializing to JSON (pass true as argument to set this, false to unset)
|
||||
The {{name}} value is set to nil even if false is passed
|
||||
{{/isNullable}}
|
||||
{{/vars}}
|
||||
{{#vendorExtensions.implements}}
|
||||
|
||||
### As{{{.}}}
|
||||
|
||||
`func (s *{{classname}}) As{{{.}}}() {{{.}}}`
|
||||
|
||||
Convenience method to wrap this instance of {{classname}} in {{{.}}}
|
||||
{{/vendorExtensions.implements}}
|
||||
{{/vendorExtensions.x-is-one-of-interface}}
|
||||
{{/isEnum}}
|
||||
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
@ -1301,4 +1301,42 @@ public class DefaultCodegenTest {
|
||||
Assert.assertTrue(roundCNext.isCircularReference);
|
||||
Assert.assertFalse(roundCOut.isCircularReference);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUseOneOfInterfaces() {
|
||||
final OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0/composed-oneof.yaml");
|
||||
final DefaultCodegen cg = new DefaultCodegen();
|
||||
cg.setUseOneOfInterfaces(true);
|
||||
cg.preprocessOpenAPI(openAPI);
|
||||
|
||||
// assert names of the response/request schema oneOf interfaces are as expected
|
||||
Assert.assertEquals(
|
||||
openAPI.getPaths()
|
||||
.get("/state")
|
||||
.getPost()
|
||||
.getRequestBody()
|
||||
.getContent()
|
||||
.get("application/json")
|
||||
.getSchema()
|
||||
.getExtensions()
|
||||
.get("x-oneOf-name"),
|
||||
"CreateState"
|
||||
);
|
||||
Assert.assertEquals(
|
||||
openAPI.getPaths()
|
||||
.get("/state")
|
||||
.getGet()
|
||||
.getResponses()
|
||||
.get("200")
|
||||
.getContent()
|
||||
.get("application/json")
|
||||
.getSchema()
|
||||
.getExtensions()
|
||||
.get("x-oneOf-name"),
|
||||
"GetState200"
|
||||
);
|
||||
// for the array schema, assert that a oneOf interface was added to schema map
|
||||
Schema items = ((ArraySchema) openAPI.getComponents().getSchemas().get("CustomOneOfArraySchema")).getItems();
|
||||
Assert.assertEquals(items.getExtensions().get("x-oneOf-name"), "CustomOneOfArraySchemaOneOf");
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,61 @@
|
||||
package org.openapitools.codegen.utils;
|
||||
|
||||
import org.openapitools.codegen.CodegenConfig;
|
||||
import org.openapitools.codegen.CodegenModel;
|
||||
import org.openapitools.codegen.CodegenProperty;
|
||||
import org.openapitools.codegen.languages.GoClientExperimentalCodegen;
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class OneOfImplementorAdditionalDataTest {
|
||||
@Test
|
||||
public void testGeneralUsage() {
|
||||
OneOfImplementorAdditionalData o = new OneOfImplementorAdditionalData("Implementor");
|
||||
|
||||
// set up all the necessary inputs for `o.addFromInterfaceModel`
|
||||
CodegenModel oneOfModel = new CodegenModel();
|
||||
oneOfModel.classname = "OneOfModel";
|
||||
oneOfModel.vars = new ArrayList<>();
|
||||
CodegenProperty cp1 = new CodegenProperty();
|
||||
cp1.baseName = "OneOfModelProperty";
|
||||
oneOfModel.vars.add(cp1);
|
||||
CodegenProperty cp2 = new CodegenProperty();
|
||||
cp2.baseName = "InterfaceModelProperty";
|
||||
oneOfModel.vars.add(cp2);
|
||||
// if the OneOfModel has interface models, we want to verify that their properties don't get
|
||||
// added to the oneOf-implementing model
|
||||
CodegenModel interfaceModel = new CodegenModel();
|
||||
interfaceModel.vars.add(cp2.clone());
|
||||
oneOfModel.interfaceModels = new ArrayList<>();
|
||||
oneOfModel.interfaceModels.add(interfaceModel);
|
||||
|
||||
List<Map<String, String>> interfaceModelImports = new ArrayList<>();
|
||||
interfaceModelImports.add(new HashMap<String, String>(){{ put("import", "foo"); }});
|
||||
|
||||
o.addFromInterfaceModel(oneOfModel, interfaceModelImports);
|
||||
|
||||
// set up all the necessary inputs for `o.addToImplementor`
|
||||
CodegenModel implModel = new CodegenModel();
|
||||
implModel.vars = new ArrayList<>();
|
||||
CodegenProperty cp3 = new CodegenProperty();
|
||||
cp3.baseName = "OtherProperty";
|
||||
cp3.hasMore = false;
|
||||
implModel.vars.add(cp3);
|
||||
List<Map<String, String>> implModelImports = new ArrayList<>();
|
||||
GoClientExperimentalCodegen cc = new GoClientExperimentalCodegen();
|
||||
cc.setModelPackage("openapi");
|
||||
|
||||
o.addToImplementor(cc, implModel, implModelImports, false);
|
||||
|
||||
// make sure all the additions were done correctly
|
||||
Assert.assertEquals(implModel.getVendorExtensions().get("implements"), new ArrayList<String>(){{add(oneOfModel.classname);}});
|
||||
Assert.assertEquals(implModelImports, interfaceModelImports);
|
||||
Assert.assertEquals(implModel.vars, new ArrayList<CodegenProperty>(){{add(cp3); add(cp1);}});
|
||||
Assert.assertTrue(implModel.vars.get(0).hasMore);
|
||||
}
|
||||
}
|
@ -42,6 +42,26 @@ paths:
|
||||
description: OK
|
||||
components:
|
||||
schemas:
|
||||
CustomOneOfSchema:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/ObjA'
|
||||
- $ref: '#/components/schemas/ObjB'
|
||||
discriminator:
|
||||
propertyName: realtype
|
||||
mapping:
|
||||
a-type: '#/components/schemas/ObjA'
|
||||
b-type: '#/components/schemas/ObjB'
|
||||
CustomOneOfArraySchema:
|
||||
type: array
|
||||
items:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/ObjA'
|
||||
- $ref: '#/components/schemas/ObjB'
|
||||
discriminator:
|
||||
propertyName: realtype
|
||||
mapping:
|
||||
a-type: '#/components/schemas/ObjA'
|
||||
b-type: '#/components/schemas/ObjB'
|
||||
ObjA:
|
||||
type: object
|
||||
properties:
|
||||
|
Loading…
x
Reference in New Issue
Block a user