NSwag
NSwag copied to clipboard
Array of `oneOf` leads to untyped list in C# if items schema is not inlined
Problem Description
In my OpenAPI spec I have a response that is an array of oneOf
s:
"schema": {
"type": "array",
"items": {
"oneOf": [
{ "$ref": "#/components/schemas/BaseSomething" },
{ "$ref": "#/components/schemas/BusinessSomething" },
{ "$ref": "#/components/schemas/OtherSomething" }
]
}
}
The type name generated for the respone is ICollection<BaseSomething>
, which makes sense.
However, if the array's items reference a component or file instead of defining the oneOf
inline, then the return type is a weaker typed ICollection<Anonymous>
, which is less useful and doesn't even compile that way.
...
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ValidationSomething"
}
}
...
"components": {
"schemas": {
"ValidationSomething": {
"oneOf": [
{ "$ref": "#/components/schemas/BaseSomething" },
{ "$ref": "#/components/schemas/BusinessSomething" },
{ "$ref": "#/components/schemas/OtherSomething" }
]
}
}
}
Expected Behavior
I would expect the C# generator to generate the same type name in both cases. Even if the array item type references a component, I would expect the type name to be resolved as if it would be inlined. In the given example I would expect a return type of ICollection<BaseSomething>
in both cases.
Complete Test Case
Here is a complete test:
[Fact]
public async Task Test()
{
// Arrange
var json = """
{
"openapi": "3.0.0",
"paths": {
"/v1/get1": {
"get": {
"operationId": "Get1",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"oneOf": [
{ "$ref": "#/components/schemas/BaseSomething" },
{ "$ref": "#/components/schemas/BusinessSomething" },
{ "$ref": "#/components/schemas/OtherSomething" }
]
}
}
}
}
}
}
}
},
"/v1/get2": {
"get": {
"operationId": "Get2",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ValidationSomething"
}
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"BaseSomething": {
"properties": {
"discriminator": {
"type": "string",
"nullable": true
}
}
},
"BusinessSomething": {
"allOf": [
{ "$ref": "#/components/schemas/BaseSomething" },
{
"type": "object",
"additionalProperties": false,
"properties": {
"userId": {
"type": "string",
"nullable": true
}
}
}
]
},
"OtherSomething": {
"allOf": [
{ "$ref": "#/components/schemas/BaseSomething" },
{
"type": "object",
"additionalProperties": false,
"properties": {
"otherId": {
"type": "string",
"nullable": true
}
}
}
]
},
"ValidationSomething": {
"oneOf": [
{ "$ref": "#/components/schemas/BaseSomething" },
{ "$ref": "#/components/schemas/BusinessSomething" },
{ "$ref": "#/components/schemas/OtherSomething" }
]
}
}
}
}
""";
var document = await OpenApiDocument.FromJsonAsync(json);
var ops = document.Paths.Select(path => path.Value.Single().Value);
var responseSchemas = ops.Select(op => op.Responses.Single().Value.Schema);
var componentSchema = (document.Components.Schemas["ValidationSomething"]);
// Act
var settings = new CSharpClientGeneratorSettings { ClassName = "MyClass" };
var generator = new CSharpClientGenerator(document, settings);
var responseTypeNames = responseSchemas.Select(schema => generator.GetTypeName(schema, false, "Test")).ToList();
var componentTypeName = generator.GetTypeName(componentSchema, false, "Component");
Assert.All(responseTypeNames, typeName =>
{
Assert.Equal("System.Collections.Generic.ICollection<BaseSomething>", typeName);
});
Assert.Equal("BaseSomething", componentTypeName);
}
Might be related to #1652, #2970