[V3] Incorrect Reference Types For Classes With More Than One Collection Properties That Points To The Same Type
Introduction
When working with OpenAPI documents, it's essential to ensure that the generated schema references are accurate and consistent. However, in certain scenarios, the generated document may contain invalid schema references for classes with multiple collection properties that point to the same type. This article will delve into the issue, provide a sample project to reproduce the problem, and discuss the expected behavior.
Describe the Bug
For response models that contain multiple collection properties referring to the same collection type, the generated document contains invalid schema references for all but one of those properties. This issue can lead to inconsistencies in the OpenAPI document, making it challenging to work with the generated schema.
OpenApi File To Reproduce
The sample OpenAPI file, SampleOpenApi_v1.0.json
, can be found in the GitHub repository here. This file contains the necessary information to reproduce the issue.
Expected Behavior
All the properties should point to the correct schema reference type. In this case, the balances
, assets
, and equities
properties should reference the Account
schema, while the deletedAccounts
property should reference the integer
type.
Screenshots/Code Snippets
The following code snippet demonstrates the issue:
/* Response Models */
public class Account
{
public int Id { get; init; }
public required string Name { get; init; }
}
public class LegalEntity
{
public required ICollection<Account> Balances { get; init; }
public required ICollection<Account> Assets { get; init; }
public required ICollection<Account> Equities { get; init; }
public required ICollection<int> DeletedAccounts { get; init; }
}
/* Api */
[Route("api/v{version:apiVersion}/accounts")]
public class LegalEntitiesController : ApiControllerBase
{
/// <summary>
/// Get legal entities
/// </summary>
/// <returns>The <see cref="LegalEntity"/></returns>
[ApiVersion("1.0")]
[ApiVersion("2.0")]
[HttpGet(Name = nameof(GetLegalEntityAccounts))]
[ProducesResponseType(typeof(IEnumerable<LegalEntity>), StatusCodes.Status200OK)]
[ProducesDefaultResponseType]
public IEnumerable<LegalEntity> GetLegalEntityAccounts()
{
return Enumerable.Range(1, 5).Select(index => new LegalEntity
{
Balances =
[
new Account { Id = index + 1, Name = "Balance 1" },
new Account { Id = index + 2, Name = "Balance 2" }
],
Assets =
[
new Account { Id = index + 3, Name = "Asset 1" },
new Account { Id = index + 4, Name = "Asset 2" }
],
Equities =
[
new Account { Id = index + 5, Name = "Equity 1" },
new Account { Id = index + 6, Name = "Equity 2" }
],
DeletedAccounts = [index + 7, index + 8]
});
}
}
The generated OpenAPI document contains the following schema:
{
...
"components": {
"schemas": {
...
"LegalEntity": {
"required": [
"balances",
"assets",
"equities",
"deletedAccounts"
],
"type": "object",
"properties": {
"balances": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Account"
}
},
"assets": {
"type": "array",
"items": {
"$ref": "#/components/schemas/#/items/properties/balances/items"
}
},
"equities": {
"type": "array",
"items": {
"$ref": "#/components/schemas/#/items/properties/balances/items"
}
},
"deletedAccounts": {
"type": "array",
"items": {
"type": "integer",
"format": "int32"
}
}
}
},
}
}
}
As you can see, the assets
and equities
properties reference the #/components/schemas/#/items/properties/balances/items
schema, which is incorrect.
Additional Context
This issue can be reproduced by creating a sample project with the following structure:
public class Account
{
public int Id { get; init; }
public required string Name { get; init; }
}
public class LegalEntity
{
public required ICollection<Account> Balances { get; init; }
public required ICollection<Account> Assets { get; init; }
public required ICollection<Account> Equities { get; init; }
public required ICollection<int> DeletedAccounts { get; init; }
}
[Route("api/v{version:apiVersion}/accounts")]
public class LegalEntitiesController : ApiControllerBase
{
/// <summary>
/// Get legal entities
/// </summary>
/// <returns>The <see cref="LegalEntity"/></returns>
[ApiVersion("1.0")]
[ApiVersion("2.0")]
[HttpGet(Name = nameof(GetLegalEntityAccounts))]
[ProducesResponseType(typeof(IEnumerable<LegalEntity>), StatusCodes.Status200OK)]
[ProducesDefaultResponseType]
public IEnumerable<LegalEntity> GetLegalEntityAccounts()
{
return Enumerable.Range(1, 5).Select(index => new LegalEntity
{
Balances =
[
new Account { Id = index + 1, Name = "Balance 1" },
new Account { Id = index + 2, Name = "Balance 2" }
],
Assets =
[
new Account { Id = index + 3, Name = "Asset 1" },
new Account { Id = index + 4, Name = "Asset 2" }
],
Equities =
[
new Account { Id = index + 5, Name = "Equity 1" },
new Account { Id = index + 6, Name = "Equity 2" }
],
DeletedAccounts = [index + 7, index + 8]
});
}
}
To reproduce the issue, simply run the project and inspect the generated OpenAPI document.
Conclusion
Q: What is the issue with incorrect reference types for classes with multiple collection properties?
A: The issue arises when a class has multiple collection properties that point to the same type. In this case, the generated OpenAPI document may contain invalid schema references for all but one of those properties.
Q: What are the symptoms of this issue?
A: The symptoms of this issue include:
- Invalid schema references in the generated OpenAPI document
- Inconsistent schema references across multiple collection properties
- Difficulty working with the generated schema due to incorrect references
Q: How can I reproduce this issue?
A: To reproduce this issue, you can create a sample project with the following structure:
public class Account
{
public int Id { get; init; }
public required string Name { get; init; }
}
public class LegalEntity
{
public required ICollection<Account> Balances { get; init; }
public required ICollection<Account> Assets { get; init; }
public required ICollection<Account> Equities { get; init; }
public required ICollection<int> DeletedAccounts { get; init; }
}
[Route("api/v{version:apiVersion}/accounts")]
public class LegalEntitiesController : ApiControllerBase
{
/// <summary>
/// Get legal entities
/// </summary>
/// <returns>The <see cref="LegalEntity"/></returns>
[ApiVersion("1.0")]
[ApiVersion("2.0")]
[HttpGet(Name = nameof(GetLegalEntityAccounts))]
[ProducesResponseType(typeof(IEnumerable<LegalEntity>), StatusCodes.Status200OK)]
[ProducesDefaultResponseType]
public IEnumerable<LegalEntity> GetLegalEntityAccounts()
{
return Enumerable.Range(1, 5).Select(index => new LegalEntity
{
Balances =
[
new Account { Id = index + 1, Name = "Balance 1" },
new Account { Id = index + 2, Name = "Balance 2" }
],
Assets =
[
new Account { Id = index + 3, Name = "Asset 1" },
new Account { Id = index + 4, Name = "Asset 2" }
],
Equities =
[
new Account { Id = index + 5, Name = "Equity 1" },
new Account { Id = index + 6, Name = "Equity 2" }
],
DeletedAccounts = [index + 7, index + 8]
});
}
}
Q: What is the expected behavior?
A: The expected behavior is that all the properties should point to the correct schema reference type. In this case, the balances
, assets
, and equities
properties should reference the Account
schema, while the deletedAccounts
property should reference the integer
type.
Q: How can I fix this issue?
A: To fix this issue, you can modify the LegalEntity
class to use a single collection property that references the Account
schema. For example:
public class LegalEntity
{
public required ICollection<Account> Accounts { get; init; }
}
Q: What are the best practices for avoiding this issue?
A: To avoid this issue, follow these best practices:
- Use a single collection property that references the desired type.
- Avoid using multiple collection properties that reference the same type.
- Use a consistent naming convention for collection properties.
- Use a consistent schema reference type for all collection properties.
Q: What are the consequences of not fixing this issue?
A: The consequences of not fixing this issue include:
- Invalid schema references in the generated OpenAPI document.
- Inconsistent schema references across multiple collection properties.
- Difficulty working with the generated schema due to incorrect references.
- Potential errors or bugs in the application due to incorrect schema references.