Skip to content
Closed
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 @@ -2802,18 +2802,31 @@ protected void updateModelForComposedSchema(CodegenModel m, Schema schema, Map<S
addImport(composed, refSchema, m, modelName);

if (allDefinitions != null && refSchema != null) {
// When the refSchema is a oneOf wrapper, do not flatten the variant
// properties into this model — that would produce an impossible
// conjunction of all variants' properties. The wrapper's own
// properties/required (shared fields declared on the wrapper itself)
// should still be inherited. The oneOf variants are tracked via
// m.interfaces. Pre-seeding visitedSchemas with the variant schemas
// suppresses recursion into them inside addProperties().
Set<Schema> visited = new HashSet<>();
if (ModelUtils.hasOneOf(refSchema) && refSchema.getOneOf() != null) {
for (Object variant : refSchema.getOneOf()) {
visited.add((Schema) variant);
}
}
if (allParents.contains(ref) && supportsMultipleInheritance) {
// multiple inheritance
addProperties(allProperties, allRequired, refSchema, new HashSet<>());
addProperties(allProperties, allRequired, refSchema, visited);
} else if (parentName != null && parentName.equals(ref) && supportsInheritance) {
// single inheritance
addProperties(allProperties, allRequired, refSchema, new HashSet<>());
addProperties(allProperties, allRequired, refSchema, visited);
} else {
// composition
Map<String, Schema> newProperties = new LinkedHashMap<>();
addProperties(newProperties, required, refSchema, new HashSet<>());
addProperties(newProperties, required, refSchema, new HashSet<>(visited));
mergeProperties(properties, newProperties);
addProperties(allProperties, allRequired, refSchema, new HashSet<>());
addProperties(allProperties, allRequired, refSchema, visited);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,13 @@ private boolean modelIsMutable(CodegenModel model, Set<String> processed) {
processed = new HashSet<String>();
}
boolean isMutable = model.allVars.stream().anyMatch(v -> !v.isReadOnly);
// oneOf/anyOf union wrappers expose a constructor per variant; the variants
// are not tracked in allVars, so fall back to the composed schema lists.
if (!isMutable && model.getComposedSchemas() != null) {
List<CodegenProperty> oneOf = model.getComposedSchemas().getOneOf();
List<CodegenProperty> anyOf = model.getComposedSchemas().getAnyOf();
isMutable = (oneOf != null && !oneOf.isEmpty()) || (anyOf != null && !anyOf.isEmpty());
}
if (!isMutable && !processed.contains(model.classname) && model.getDiscriminator() != null && model.getDiscriminator().getMappedModels() != null) {
processed.add(model.classname);
isMutable = modelIsMutable(model, processed);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1589,6 +1589,11 @@ public static String getParentName(Schema composedSchema, Map<String, Schema> al
} else if (hasOrInheritsDiscriminator(s, allSchemas, new ArrayList<Schema>())) {
// discriminator.propertyName is used or x-parent is used
parentNameCandidates.add(parentName);
} else if (hasOneOf(s)) {
// a $ref to a oneOf schema is treated as a parent even without
// a discriminator, to avoid flattening union type variants into
// an impossible conjunction of all variant properties
parentNameCandidates.add(parentName);
} else {
// not a parent since discriminator.propertyName or x-parent is not set
hasAmbiguousParents = true;
Expand Down Expand Up @@ -1658,6 +1663,13 @@ private static List<String> getAllParentsName(
if (includeAncestors && isComposedSchema(s)) {
names.addAll(getAllParentsName(s, allSchemas, true, seenNames));
}
} else if (hasOneOf(s)) {
// a $ref to a oneOf schema is treated as a parent even without
// a discriminator
names.add(parentName);
if (includeAncestors && isComposedSchema(s)) {
names.addAll(getAllParentsName(s, allSchemas, true, seenNames));
}
} else {
// not a parent since discriminator.propertyName is not set
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1171,6 +1171,79 @@ public void testAllOfRequired() {
assertEquals(getRequiredVars(childModel), Collections.singletonList("name"));
}

@Test
public void testAllOfWithOneOfRefNoDiscriminator() {
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/allOf_oneOf_noDiscriminator.yaml");
DefaultCodegen codegen = new DefaultCodegen();
codegen.supportsInheritance = true;
codegen.setOpenAPI(openAPI);

// ParentC uses allOf with a $ref to Child (a oneOf without discriminator).
// Child should be recognized as a parent, not flattened.
Schema parentCSchema = openAPI.getComponents().getSchemas().get("ParentC");
CodegenModel parentCModel = codegen.fromModel("ParentC", parentCSchema);
assertEquals("Child", parentCModel.parentSchema);
assertEquals("Child", parentCModel.parent);

// Only the own property "type" should be in vars, not the oneOf variant properties
List<String> varNames = parentCModel.vars.stream().map(v -> v.name).collect(Collectors.toList());
assertTrue(varNames.contains("type"), "vars should contain 'type'");
assertFalse(varNames.contains("xOnlyField"), "vars should not contain variant-specific 'xOnlyField'");
assertFalse(varNames.contains("yOnlyField"), "vars should not contain variant-specific 'yOnlyField'");
assertFalse(varNames.contains("zOnlyField"), "vars should not contain variant-specific 'zOnlyField'");

// allVars should also not contain flattened variant properties
List<String> allVarNames = parentCModel.allVars.stream().map(v -> v.name).collect(Collectors.toList());
assertFalse(allVarNames.contains("xOnlyField"), "allVars should not contain variant-specific 'xOnlyField'");
assertFalse(allVarNames.contains("yOnlyField"), "allVars should not contain variant-specific 'yOnlyField'");
assertFalse(allVarNames.contains("zOnlyField"), "allVars should not contain variant-specific 'zOnlyField'");
}

@Test
public void testAllOfWithOneOfRefNoDiscriminatorNoInheritance() {
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/allOf_oneOf_noDiscriminator.yaml");
DefaultCodegen codegen = new DefaultCodegen();
codegen.supportsInheritance = false;
codegen.setOpenAPI(openAPI);

// Even without supportsInheritance, variant properties should not be flattened
Schema parentCSchema = openAPI.getComponents().getSchemas().get("ParentC");
CodegenModel parentCModel = codegen.fromModel("ParentC", parentCSchema);

List<String> varNames = parentCModel.vars.stream().map(v -> v.name).collect(Collectors.toList());
assertFalse(varNames.contains("xOnlyField"), "vars should not contain variant-specific 'xOnlyField'");
assertFalse(varNames.contains("yOnlyField"), "vars should not contain variant-specific 'yOnlyField'");
assertFalse(varNames.contains("zOnlyField"), "vars should not contain variant-specific 'zOnlyField'");
}

@Test
public void testAllOfWithOneOfRefPreservesWrapperProperties() {
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/allOf_oneOf_noDiscriminator.yaml");
DefaultCodegen codegen = new DefaultCodegen();
codegen.supportsInheritance = true;
codegen.setOpenAPI(openAPI);

// ChildWithSharedFields is a oneOf wrapper that also declares its own
// properties (wrapperOptionalField, wrapperRequiredField). When ParentWithSharedFields
// inherits from it via allOf, those wrapper-level properties must still flow
// through to allProperties/allRequired — only the variant-specific properties
// should be suppressed.
Schema parentSchema = openAPI.getComponents().getSchemas().get("ParentWithSharedFields");
CodegenModel parentModel = codegen.fromModel("ParentWithSharedFields", parentSchema);

List<String> allVarNames = parentModel.allVars.stream().map(v -> v.name).collect(Collectors.toList());
assertTrue(allVarNames.contains("wrapperOptionalField"), "allVars should contain wrapper-level 'wrapperOptionalField'");
assertTrue(allVarNames.contains("wrapperRequiredField"), "allVars should contain wrapper-level 'wrapperRequiredField'");
assertTrue(allVarNames.contains("extraField"), "allVars should contain inline allOf 'extraField'");
assertFalse(allVarNames.contains("xOnlyField"), "allVars should not contain variant-specific 'xOnlyField'");
assertFalse(allVarNames.contains("yOnlyField"), "allVars should not contain variant-specific 'yOnlyField'");
assertFalse(allVarNames.contains("zOnlyField"), "allVars should not contain variant-specific 'zOnlyField'");

List<String> requiredVarNames = parentModel.requiredVars.stream().map(v -> v.name).collect(Collectors.toList());
assertTrue(requiredVarNames.contains("wrapperRequiredField"), "requiredVars should include wrapper-level required 'wrapperRequiredField'");
assertTrue(requiredVarNames.contains("extraField"), "requiredVars should include inline allOf required 'extraField'");
}

@Test
public void testAllOfSingleAndDoubleRefWithOwnPropsNoDiscriminator() {
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/allOf_composition.yaml");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
openapi: 3.0.2
info:
title: allOf with oneOf (no discriminator) test
version: 1.0.0
paths:
/parent:
get:
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/ParentC'
/parent-shared:
get:
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/ParentWithSharedFields'
components:
schemas:
ParentC:
allOf:
- type: object
required: [type]
properties:
type:
type: string
- $ref: "#/components/schemas/Child"

Child:
oneOf:
- $ref: "#/components/schemas/ChildX"
- $ref: "#/components/schemas/ChildY"
- $ref: "#/components/schemas/ChildZ"

ParentWithSharedFields:
allOf:
- $ref: "#/components/schemas/ChildWithSharedFields"
- type: object
required: [extraField]
properties:
extraField:
type: string

ChildWithSharedFields:
type: object
required: [wrapperRequiredField]
properties:
wrapperOptionalField:
type: string
wrapperRequiredField:
type: string
oneOf:
- $ref: "#/components/schemas/ChildX"
- $ref: "#/components/schemas/ChildY"
- $ref: "#/components/schemas/ChildZ"

ChildX:
type: object
required: [kind, sharedField, xOnlyField]
properties:
kind:
type: string
sharedField:
type: string
xOnlyField:
type: string

ChildY:
type: object
required: [kind, sharedField, yOnlyField]
properties:
kind:
type: string
sharedField:
type: string
yOnlyField:
type: string

ChildZ:
type: object
required: [kind, sharedField, zOnlyField]
properties:
kind:
type: string
sharedField:
type: string
zOnlyField:
type: integer
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public partial class IconsSizeParameter : IValidatableObject
/// </summary>
/// <param name="iconsSizeParameterAnyOf"></param>
/// <param name="int"></param>
internal IconsSizeParameter(Option<IconsSizeParameterAnyOf?> iconsSizeParameterAnyOf, Option<int?> @int)
public IconsSizeParameter(Option<IconsSizeParameterAnyOf?> iconsSizeParameterAnyOf, Option<int?> @int)
{
IconsSizeParameterAnyOfOption = iconsSizeParameterAnyOf;
IntOption = @int;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public partial class OneOfArrayRequest : IValidatableObject
/// Initializes a new instance of the <see cref="OneOfArrayRequest" /> class.
/// </summary>
/// <param name="list"></param>
internal OneOfArrayRequest(List<string> list)
public OneOfArrayRequest(List<string> list)
{
List = list;
OnCreated();
Expand All @@ -44,7 +44,7 @@ internal OneOfArrayRequest(List<string> list)
/// Initializes a new instance of the <see cref="OneOfArrayRequest" /> class.
/// </summary>
/// <param name="list1"></param>
internal OneOfArrayRequest(List<TestObject> list1)
public OneOfArrayRequest(List<TestObject> list1)
{
List1 = list1;
OnCreated();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ The value may be a shape or the 'null' value. The 'nullable' attribute was intro

Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**ShapeType** | **string** | |

[[Back to Model list]](../../README.md#documentation-for-models) [[Back to API list]](../../README.md#documentation-for-api-endpoints) [[Back to README]](../../README.md)

Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**ShapeType** | **string** | |

[[Back to Model list]](../../README.md#documentation-for-models) [[Back to API list]](../../README.md#documentation-for-api-endpoints) [[Back to README]](../../README.md)

Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ The value may be a shape or the 'null' value. This is introduced in OAS schema >

Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**ShapeType** | **string** | |

[[Back to Model list]](../../README.md#documentation-for-models) [[Back to API list]](../../README.md#documentation-for-api-endpoints) [[Back to README]](../../README.md)

Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,6 @@ public override NullableShape Read(ref Utf8JsonReader utf8JsonReader, Type typeT

JsonTokenType startingTokenType = utf8JsonReader.TokenType;

Option<string?> shapeType = default;

Quadrilateral? quadrilateral = null;
Triangle? triangle = null;

Expand Down Expand Up @@ -167,21 +165,12 @@ public override NullableShape Read(ref Utf8JsonReader utf8JsonReader, Type typeT

switch (localVarJsonPropertyName)
{
case "shapeType":
shapeType = new Option<string?>(utf8JsonReader.GetString()!);
break;
default:
break;
}
}
}

if (!shapeType.IsSet)
throw new ArgumentException("Property is required for class NullableShape.", nameof(shapeType));

if (shapeType.IsSet && shapeType.Value == null)
throw new ArgumentNullException(nameof(shapeType), "Property is not nullable for class NullableShape.");

if (quadrilateral != null)
return new NullableShape(quadrilateral);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public partial class OneOfString : IValidatableObject
/// Initializes a new instance of the <see cref="OneOfString" /> class.
/// </summary>
/// <param name="string"></param>
internal OneOfString(string @string)
public OneOfString(string @string)
{
String = @string;
OnCreated();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public partial class PolymorphicProperty : IValidatableObject
/// Initializes a new instance of the <see cref="PolymorphicProperty" /> class.
/// </summary>
/// <param name="bool"></param>
internal PolymorphicProperty(bool @bool)
public PolymorphicProperty(bool @bool)
{
Bool = @bool;
OnCreated();
Expand All @@ -44,7 +44,7 @@ internal PolymorphicProperty(bool @bool)
/// Initializes a new instance of the <see cref="PolymorphicProperty" /> class.
/// </summary>
/// <param name="string"></param>
internal PolymorphicProperty(string @string)
public PolymorphicProperty(string @string)
{
String = @string;
OnCreated();
Expand All @@ -54,7 +54,7 @@ internal PolymorphicProperty(string @string)
/// Initializes a new instance of the <see cref="PolymorphicProperty" /> class.
/// </summary>
/// <param name="object"></param>
internal PolymorphicProperty(Object @object)
public PolymorphicProperty(Object @object)
{
Object = @object;
OnCreated();
Expand All @@ -64,7 +64,7 @@ internal PolymorphicProperty(Object @object)
/// Initializes a new instance of the <see cref="PolymorphicProperty" /> class.
/// </summary>
/// <param name="list"></param>
internal PolymorphicProperty(List<string> list)
public PolymorphicProperty(List<string> list)
{
List = list;
OnCreated();
Expand Down
Loading
Loading