Skip to content

Commit 178dda5

Browse files
authored
OpenAPI: Fix Circular reference in specific order gives empty schema (#63511)
* Add circular schema example * Fix empty schema in certain orderings of schemas. This is fixed by not adding/registering a schema when it contains a reference, as it is a incomplete/unresolved schema * Revert launchsettings.json * Add unit tests for circular references and remove it from the sample * Move tests to OpenApiSchemaReferenceTransformerTests * Fix NRT warnings
1 parent 448e602 commit 178dda5

File tree

2 files changed

+144
-1
lines changed

2 files changed

+144
-1
lines changed

src/OpenApi/src/Services/Schemas/OpenApiSchemaService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ internal static IOpenApiSchema ResolveReferenceForSchema(OpenApiDocument documen
337337
if (schema.Metadata.TryGetValue(OpenApiConstants.SchemaId, out var schemaId) &&
338338
schemaId is string schemaIdString)
339339
{
340-
return document.AddOpenApiSchemaByReference(schemaIdString, schema);
340+
return new OpenApiSchemaReference(schemaIdString, document);
341341
}
342342
var relativeSchemaId = $"#/components/schemas/{rootSchemaId}{refIdString.Replace("#", string.Empty)}";
343343
return new OpenApiSchemaReference(relativeSchemaId, document);

src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Transformers/Implementations/OpenApiSchemaReferenceTransformerTests.cs

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,6 +1004,124 @@ await VerifyOpenApiDocument(builder, document =>
10041004
});
10051005
}
10061006

1007+
// Test for: https://github.com/dotnet/aspnetcore/issues/63503
1008+
[Fact]
1009+
public async Task HandlesCircularReferencesRegardlessOfPropertyOrder_SelfFirst()
1010+
{
1011+
var builder = CreateBuilder();
1012+
builder.MapPost("/", (DirectCircularModelSelfFirst dto) => { });
1013+
1014+
// Assert
1015+
await VerifyOpenApiDocument(builder, document =>
1016+
{
1017+
Assert.NotNull(document.Components?.Schemas);
1018+
var schema = document.Components.Schemas["DirectCircularModelSelfFirst"];
1019+
Assert.Equal(JsonSchemaType.Object, schema.Type);
1020+
Assert.NotNull(schema.Properties);
1021+
Assert.Collection(schema.Properties,
1022+
property =>
1023+
{
1024+
Assert.Equal("self", property.Key);
1025+
var reference = Assert.IsType<OpenApiSchemaReference>(property.Value);
1026+
Assert.Equal("#/components/schemas/DirectCircularModelSelfFirst", reference.Reference.ReferenceV3);
1027+
},
1028+
property =>
1029+
{
1030+
Assert.Equal("referenced", property.Key);
1031+
var reference = Assert.IsType<OpenApiSchemaReference>(property.Value);
1032+
});
1033+
1034+
// Verify that it does not result in an empty schema for a referenced schema
1035+
var referencedSchema = document.Components.Schemas["ReferencedModel"];
1036+
Assert.NotNull(referencedSchema.Properties);
1037+
Assert.NotEmpty(referencedSchema.Properties);
1038+
var idProperty = Assert.Single(referencedSchema.Properties);
1039+
Assert.Equal("id", idProperty.Key);
1040+
var idPropertySchema = Assert.IsType<OpenApiSchema>(idProperty.Value);
1041+
Assert.Equal(JsonSchemaType.Integer, idPropertySchema.Type);
1042+
});
1043+
}
1044+
1045+
// Test for: https://github.com/dotnet/aspnetcore/issues/63503
1046+
[Fact]
1047+
public async Task HandlesCircularReferencesRegardlessOfPropertyOrder_SelfLast()
1048+
{
1049+
var builder = CreateBuilder();
1050+
builder.MapPost("/", (DirectCircularModelSelfLast dto) => { });
1051+
1052+
await VerifyOpenApiDocument(builder, document =>
1053+
{
1054+
Assert.NotNull(document.Components?.Schemas);
1055+
var schema = document.Components.Schemas["DirectCircularModelSelfLast"];
1056+
Assert.Equal(JsonSchemaType.Object, schema.Type);
1057+
Assert.NotNull(schema.Properties);
1058+
Assert.Collection(schema.Properties,
1059+
property =>
1060+
{
1061+
Assert.Equal("referenced", property.Key);
1062+
var reference = Assert.IsType<OpenApiSchemaReference>(property.Value);
1063+
},
1064+
property =>
1065+
{
1066+
Assert.Equal("self", property.Key);
1067+
var reference = Assert.IsType<OpenApiSchemaReference>(property.Value);
1068+
Assert.Equal("#/components/schemas/DirectCircularModelSelfLast", reference.Reference.ReferenceV3);
1069+
});
1070+
1071+
// Verify that it does not result in an empty schema for a referenced schema
1072+
var referencedSchema = document.Components.Schemas["ReferencedModel"];
1073+
Assert.NotNull(referencedSchema.Properties);
1074+
Assert.NotEmpty(referencedSchema.Properties);
1075+
var idProperty = Assert.Single(referencedSchema.Properties);
1076+
Assert.Equal("id", idProperty.Key);
1077+
var idPropertySchema = Assert.IsType<OpenApiSchema>(idProperty.Value);
1078+
Assert.Equal(JsonSchemaType.Integer, idPropertySchema.Type);
1079+
});
1080+
}
1081+
1082+
// Test for: https://github.com/dotnet/aspnetcore/issues/63503
1083+
[Fact]
1084+
public async Task HandlesCircularReferencesRegardlessOfPropertyOrder_MultipleSelf()
1085+
{
1086+
var builder = CreateBuilder();
1087+
builder.MapPost("/", (DirectCircularModelMultiple dto) => { });
1088+
1089+
await VerifyOpenApiDocument(builder, document =>
1090+
{
1091+
Assert.NotNull(document.Components?.Schemas);
1092+
var schema = document.Components.Schemas["DirectCircularModelMultiple"];
1093+
Assert.Equal(JsonSchemaType.Object, schema.Type);
1094+
Assert.NotNull(schema.Properties);
1095+
Assert.Collection(schema.Properties,
1096+
property =>
1097+
{
1098+
Assert.Equal("selfFirst", property.Key);
1099+
var reference = Assert.IsType<OpenApiSchemaReference>(property.Value);
1100+
Assert.Equal("#/components/schemas/DirectCircularModelMultiple", reference.Reference.ReferenceV3);
1101+
},
1102+
property =>
1103+
{
1104+
Assert.Equal("referenced", property.Key);
1105+
var reference = Assert.IsType<OpenApiSchemaReference>(property.Value);
1106+
},
1107+
property =>
1108+
{
1109+
Assert.Equal("selfLast", property.Key);
1110+
var reference = Assert.IsType<OpenApiSchemaReference>(property.Value);
1111+
Assert.Equal("#/components/schemas/DirectCircularModelMultiple", reference.Reference.ReferenceV3);
1112+
});
1113+
1114+
// Verify that it does not result in an empty schema for a referenced schema
1115+
var referencedSchema = document.Components.Schemas["ReferencedModel"];
1116+
Assert.NotNull(referencedSchema.Properties);
1117+
Assert.NotEmpty(referencedSchema.Properties);
1118+
var idProperty = Assert.Single(referencedSchema.Properties);
1119+
Assert.Equal("id", idProperty.Key);
1120+
var idPropertySchema = Assert.IsType<OpenApiSchema>(idProperty.Value);
1121+
Assert.Equal(JsonSchemaType.Integer, idPropertySchema.Type);
1122+
});
1123+
}
1124+
10071125
// Test models for issue 61194
10081126
private class Config
10091127
{
@@ -1060,5 +1178,30 @@ public sealed class RefUser
10601178
public string Name { get; set; } = "";
10611179
public string Email { get; set; } = "";
10621180
}
1181+
1182+
// Test models for issue 63503
1183+
private class DirectCircularModelSelfFirst
1184+
{
1185+
public DirectCircularModelSelfFirst Self { get; set; } = null!;
1186+
public ReferencedModel Referenced { get; set; } = null!;
1187+
}
1188+
1189+
private class DirectCircularModelSelfLast
1190+
{
1191+
public ReferencedModel Referenced { get; set; } = null!;
1192+
public DirectCircularModelSelfLast Self { get; set; } = null!;
1193+
}
1194+
1195+
private class DirectCircularModelMultiple
1196+
{
1197+
public DirectCircularModelMultiple SelfFirst { get; set; } = null!;
1198+
public ReferencedModel Referenced { get; set; } = null!;
1199+
public DirectCircularModelMultiple SelfLast { get; set; } = null!;
1200+
}
1201+
1202+
private class ReferencedModel
1203+
{
1204+
public int Id { get; set; }
1205+
}
10631206
}
10641207
#nullable restore

0 commit comments

Comments
 (0)