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 @@ -2756,6 +2756,13 @@ protected void updateModelForComposedSchema(CodegenModel m, Schema schema, Map<S

// interfaces (schemas defined in allOf, anyOf, oneOf)
List<Schema> interfaces = ModelUtils.getInterfaces(composed);

// For anyOf/oneOf, required fields should be the intersection across members,
// not the union. A field is only guaranteed present if ALL members require it.
boolean isAnyOfOrOneOf = (composed.getAnyOf() != null && !composed.getAnyOf().isEmpty())
|| (composed.getOneOf() != null && !composed.getOneOf().isEmpty());
List<Set<String>> perMemberRequiredSets = isAnyOfOrOneOf ? new ArrayList<>() : null;

if (!interfaces.isEmpty()) {
// m.interfaces is for backward compatibility
if (m.interfaces == null)
Expand Down Expand Up @@ -2816,7 +2823,14 @@ protected void updateModelForComposedSchema(CodegenModel m, Schema schema, Map<S
} else {
// composition
Map<String, Schema> newProperties = new LinkedHashMap<>();
addProperties(newProperties, required, refSchema, new HashSet<>());
if (isAnyOfOrOneOf) {
// Collect required fields per-member for later intersection
List<String> memberRequired = new ArrayList<>();
addProperties(newProperties, memberRequired, refSchema, new HashSet<>());
perMemberRequiredSets.add(new HashSet<>(memberRequired));
} else {
addProperties(newProperties, required, refSchema, new HashSet<>());
}
mergeProperties(properties, newProperties);
addProperties(allProperties, allRequired, refSchema, new HashSet<>());
}
Expand Down Expand Up @@ -2857,8 +2871,15 @@ protected void updateModelForComposedSchema(CodegenModel m, Schema schema, Map<S
for (Schema component : interfaces) {
if (component.get$ref() == null) {
if (component != null) {
// component is the child schema
addProperties(properties, required, component, new HashSet<>());
if (isAnyOfOrOneOf) {
// Collect required fields per-member for later intersection
List<String> memberRequired = new ArrayList<>();
addProperties(properties, memberRequired, component, new HashSet<>());
perMemberRequiredSets.add(new HashSet<>(memberRequired));
} else {
// 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<>());
Expand All @@ -2868,6 +2889,16 @@ protected void updateModelForComposedSchema(CodegenModel m, Schema schema, Map<S
}
}

// For anyOf/oneOf, compute the intersection of required fields across all members.
// A field is only required in the merged model if ALL members require it.
if (isAnyOfOrOneOf && !perMemberRequiredSets.isEmpty()) {
Set<String> intersected = new HashSet<>(perMemberRequiredSets.get(0));
for (int i = 1; i < perMemberRequiredSets.size(); i++) {
intersected.retainAll(perMemberRequiredSets.get(i));
}
required.addAll(intersected);
}

if (composed.getRequired() != null) {
required.addAll(composed.getRequired());
allRequired.addAll(composed.getRequired());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,12 @@
import org.openapitools.codegen.DefaultCodegen;
import org.openapitools.codegen.TestUtils;
import org.openapitools.codegen.languages.Swift6ClientCodegen;
import org.openapitools.codegen.utils.ModelUtils;
import org.testng.Assert;
import org.testng.annotations.Test;

import java.util.Map;

@SuppressWarnings("static-method")
public class Swift6ClientCodegenModelTest {

Expand Down Expand Up @@ -163,4 +166,74 @@ public void useCustomDateTimeTest() {
Assert.assertFalse(property7.isContainer);
}

@Test(description = "anyOf with different required fields should use intersection for required", enabled = true)
public void anyOfRequiredFieldsIntersectionTest() {
// VoteResponse requires: status, voteId
// APIError requires: status, reason, code
// Intersection: only "status" should be required in the merged model
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/swift6_anyof_required.yaml");
final Swift6ClientCodegen codegen = new Swift6ClientCodegen();
codegen.setOpenAPI(openAPI);
codegen.processOpts();

// The inline model resolver creates a model for the anyOf response.
// Find the composed schema that merges VoteResponse and APIError.
Map<String, Schema> schemas = ModelUtils.getSchemas(openAPI);
Schema composedSchema = null;
String composedName = null;
for (Map.Entry<String, Schema> entry : schemas.entrySet()) {
Schema s = entry.getValue();
if (s.getAnyOf() != null && !s.getAnyOf().isEmpty()) {
composedSchema = s;
composedName = entry.getKey();
break;
}
}
Assert.assertNotNull(composedSchema, "Should find an anyOf composed schema");

final CodegenModel cm = codegen.fromModel(composedName, composedSchema);

// "status" is required in BOTH VoteResponse and APIError -> should be required
CodegenProperty statusProp = cm.vars.stream()
.filter(p -> p.baseName.equals("status"))
.findFirst().orElse(null);
Assert.assertNotNull(statusProp, "status property should exist");
Assert.assertTrue(statusProp.required, "status should be required (present in all anyOf members)");

// "voteId" is required only in VoteResponse, not in APIError -> should NOT be required
CodegenProperty voteIdProp = cm.vars.stream()
.filter(p -> p.baseName.equals("voteId"))
.findFirst().orElse(null);
Assert.assertNotNull(voteIdProp, "voteId property should exist");
Assert.assertFalse(voteIdProp.required, "voteId should NOT be required (only in VoteResponse, not APIError)");

// "reason" is required only in APIError, not in VoteResponse -> should NOT be required
CodegenProperty reasonProp = cm.vars.stream()
.filter(p -> p.baseName.equals("reason"))
.findFirst().orElse(null);
Assert.assertNotNull(reasonProp, "reason property should exist");
Assert.assertFalse(reasonProp.required, "reason should NOT be required (only in APIError, not VoteResponse)");

// "code" is required only in APIError, not in VoteResponse -> should NOT be required
CodegenProperty codeProp = cm.vars.stream()
.filter(p -> p.baseName.equals("code"))
.findFirst().orElse(null);
Assert.assertNotNull(codeProp, "code property should exist");
Assert.assertFalse(codeProp.required, "code should NOT be required (only in APIError, not VoteResponse)");

// "isVerified" is optional in VoteResponse, not in APIError -> should NOT be required
CodegenProperty isVerifiedProp = cm.vars.stream()
.filter(p -> p.baseName.equals("isVerified"))
.findFirst().orElse(null);
Assert.assertNotNull(isVerifiedProp, "isVerified property should exist");
Assert.assertFalse(isVerifiedProp.required, "isVerified should NOT be required");

// "secondaryCode" is optional in APIError -> should NOT be required
CodegenProperty secondaryCodeProp = cm.vars.stream()
.filter(p -> p.baseName.equals("secondaryCode"))
.findFirst().orElse(null);
Assert.assertNotNull(secondaryCodeProp, "secondaryCode property should exist");
Assert.assertFalse(secondaryCodeProp.required, "secondaryCode should NOT be required");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
openapi: 3.0.0
info:
title: anyOf Required Fields Test
version: 1.0.0
paths:
/vote:
post:
operationId: voteOnItem
responses:
'200':
description: Success or error response
content:
application/json:
schema:
anyOf:
- $ref: '#/components/schemas/VoteResponse'
- $ref: '#/components/schemas/APIError'
components:
schemas:
APIStatus:
type: string
enum:
- success
- failed
VoteResponse:
type: object
required:
- status
- voteId
properties:
status:
$ref: '#/components/schemas/APIStatus'
voteId:
type: string
isVerified:
type: boolean
APIError:
type: object
required:
- status
- reason
- code
properties:
status:
$ref: '#/components/schemas/APIStatus'
reason:
type: string
code:
type: string
secondaryCode:
type: string
16 changes: 13 additions & 3 deletions samples/client/others/go/oneof-anyof-required/docs/Object.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@

Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**Field1** | **string** | Specifies an action name to be used with the Android Intent class. |
**Field2** | **string** | Specifies an action name to be used with the Android Intent class. |
**Field1** | Pointer to **string** | Specifies an action name to be used with the Android Intent class. | [optional]
**Field2** | Pointer to **string** | Specifies an action name to be used with the Android Intent class. | [optional]

## Methods

### NewObject

`func NewObject(field1 string, field2 string, ) *Object`
`func NewObject() *Object`

NewObject instantiates a new Object object
This constructor will assign default values to properties that have it defined,
Expand Down Expand Up @@ -45,6 +45,11 @@ and a boolean to check if the value has been set.

SetField1 sets Field1 field to given value.

### HasField1

`func (o *Object) HasField1() bool`

HasField1 returns a boolean if a field has been set.

### GetField2

Expand All @@ -65,6 +70,11 @@ and a boolean to check if the value has been set.

SetField2 sets Field2 field to given value.

### HasField2

`func (o *Object) HasField2() bool`

HasField2 returns a boolean if a field has been set.


[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
Expand Down
16 changes: 13 additions & 3 deletions samples/client/others/go/oneof-anyof-required/docs/Object2.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@

Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**Field1** | **string** | Specifies an action name to be used with the Android Intent class. |
**Field2** | **string** | Specifies an action name to be used with the Android Intent class. |
**Field1** | Pointer to **string** | Specifies an action name to be used with the Android Intent class. | [optional]
**Field2** | Pointer to **string** | Specifies an action name to be used with the Android Intent class. | [optional]

## Methods

### NewObject2

`func NewObject2(field1 string, field2 string, ) *Object2`
`func NewObject2() *Object2`

NewObject2 instantiates a new Object2 object
This constructor will assign default values to properties that have it defined,
Expand Down Expand Up @@ -45,6 +45,11 @@ and a boolean to check if the value has been set.

SetField1 sets Field1 field to given value.

### HasField1

`func (o *Object2) HasField1() bool`

HasField1 returns a boolean if a field has been set.

### GetField2

Expand All @@ -65,6 +70,11 @@ and a boolean to check if the value has been set.

SetField2 sets Field2 field to given value.

### HasField2

`func (o *Object2) HasField2() bool`

HasField2 returns a boolean if a field has been set.


[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@

Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**Field1** | **string** | Specifies an action name to be used with the Android Intent class. |
**Field1** | Pointer to **string** | Specifies an action name to be used with the Android Intent class. | [optional]
**Type** | Pointer to **string** | | [optional]

## Methods

### NewObject

`func NewObject(field1 string, ) *Object`
`func NewObject() *Object`

NewObject instantiates a new Object object
This constructor will assign default values to properties that have it defined,
Expand Down Expand Up @@ -45,6 +45,11 @@ and a boolean to check if the value has been set.

SetField1 sets Field1 field to given value.

### HasField1

`func (o *Object) HasField1() bool`

HasField1 returns a boolean if a field has been set.

### GetType

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
| Name | Type | Description | Notes |
| ------------ | ------------- | ------------- | ------------- |
| **id** | **kotlin.Long** | | |
| **username** | **kotlin.String** | | |
| **name** | **kotlin.String** | | |
| **username** | **kotlin.String** | | [optional] |
| **name** | **kotlin.String** | | [optional] |



Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
## Properties
| Name | Type | Description | Notes |
| ------------ | ------------- | ------------- | ------------- |
| **id** | **kotlin.Long** | | |
| **username** | **kotlin.String** | | |
| **name** | **kotlin.String** | | |
| **id** | **kotlin.Long** | | [optional] |
| **username** | **kotlin.String** | | [optional] |
| **name** | **kotlin.String** | | [optional] |



Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
| Name | Type | Description | Notes |
| ------------ | ------------- | ------------- | ------------- |
| **id** | **kotlin.Long** | | |
| **username** | **kotlin.String** | | |
| **name** | **kotlin.String** | | |
| **username** | **kotlin.String** | | [optional] |
| **name** | **kotlin.String** | | [optional] |



Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
## Properties
| Name | Type | Description | Notes |
| ------------ | ------------- | ------------- | ------------- |
| **id** | **kotlin.Long** | | |
| **username** | **kotlin.String** | | |
| **name** | **kotlin.String** | | |
| **id** | **kotlin.Long** | | [optional] |
| **username** | **kotlin.String** | | [optional] |
| **name** | **kotlin.String** | | [optional] |



Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading