Skip to content

Commit b2a9a92

Browse files
Fix missing OpenAPI 3.1 $ref properties (#3859)
- Fix missing properties on OpenAPI 3.1 schema references (`default`, `deprecated`). - Refactor to halve the number of calls to `Type.GetGenericTypeDefinition()` in the worst case. Resolves #3858.
1 parent ef008b6 commit b2a9a92

File tree

25 files changed

+10111
-23
lines changed

25 files changed

+10111
-23
lines changed

src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/SchemaGenerator.cs

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,28 @@ private IOpenApiSchema GenerateSchemaForMember(
4949
? GeneratePolymorphicSchema(schemaRepository, knownTypesDataContracts)
5050
: GenerateConcreteSchema(dataContract, schemaRepository);
5151

52-
if (_generatorOptions.UseAllOfToExtendReferenceSchemas && schema is OpenApiSchemaReference reference)
52+
if (schema is OpenApiSchemaReference reference)
5353
{
54-
schema = new OpenApiSchema() { AllOf = [reference] };
54+
if (_generatorOptions.UseAllOfToExtendReferenceSchemas)
55+
{
56+
schema = new OpenApiSchema() { AllOf = [reference] };
57+
}
58+
else
59+
{
60+
var customAttributes = memberInfo.GetInlineAndMetadataAttributes();
61+
62+
var defaultValueAttribute = customAttributes.OfType<DefaultValueAttribute>().FirstOrDefault();
63+
if (defaultValueAttribute != null)
64+
{
65+
reference.Default = GenerateDefaultValue(dataContract, modelType, defaultValueAttribute.Value);
66+
}
67+
68+
var obsoleteAttribute = customAttributes.OfType<ObsoleteAttribute>().FirstOrDefault();
69+
if (obsoleteAttribute != null)
70+
{
71+
reference.Deprecated = true;
72+
}
73+
}
5574
}
5675

5776
if (schema is OpenApiSchema concrete)
@@ -96,9 +115,11 @@ private IOpenApiSchema GenerateSchemaForMember(
96115
.Where(t => t.IsGenericType)
97116
.ToArray();
98117

99-
var isDictionaryType =
100-
genericTypes.Any(t => t.GetGenericTypeDefinition() == typeof(IDictionary<,>)) ||
101-
genericTypes.Any(t => t.GetGenericTypeDefinition() == typeof(IReadOnlyDictionary<,>));
118+
var isDictionaryType = genericTypes.Any(static (type) =>
119+
{
120+
var definition = type.GetGenericTypeDefinition();
121+
return definition == typeof(IDictionary<,>) || definition == typeof(IReadOnlyDictionary<,>);
122+
});
102123

103124
if (isDictionaryType && schema.AdditionalProperties is OpenApiSchema additionalProperties)
104125
{
@@ -138,9 +159,25 @@ private IOpenApiSchema GenerateSchemaForParameter(
138159
? GeneratePolymorphicSchema(schemaRepository, knownTypesDataContracts)
139160
: GenerateConcreteSchema(dataContract, schemaRepository);
140161

141-
if (_generatorOptions.UseAllOfToExtendReferenceSchemas && schema is OpenApiSchemaReference reference)
162+
if (schema is OpenApiSchemaReference reference)
142163
{
143-
schema = new OpenApiSchema() { AllOf = [reference] };
164+
if (_generatorOptions.UseAllOfToExtendReferenceSchemas)
165+
{
166+
schema = new OpenApiSchema() { AllOf = [reference] };
167+
}
168+
else
169+
{
170+
var customAttributes = parameterInfo.GetCustomAttributes();
171+
172+
var defaultValue = parameterInfo.HasDefaultValue
173+
? parameterInfo.DefaultValue
174+
: customAttributes.OfType<DefaultValueAttribute>().FirstOrDefault()?.Value;
175+
176+
if (defaultValue != null)
177+
{
178+
reference.Default = GenerateDefaultValue(dataContract, modelType, defaultValue);
179+
}
180+
}
144181
}
145182

146183
if (schema is OpenApiSchema concrete)
@@ -438,7 +475,7 @@ private OpenApiSchema CreateObjectSchema(DataContract dataContract, SchemaReposi
438475

439476
var memberType = dataProperty.MemberType;
440477

441-
schema.Properties[dataProperty.Name] = (dataProperty.MemberInfo != null)
478+
schema.Properties[dataProperty.Name] = dataProperty.MemberInfo != null
442479
? GenerateSchemaForMember(memberType, schemaRepository, dataProperty.MemberInfo, dataProperty)
443480
: GenerateSchemaForType(memberType, schemaRepository);
444481

test/Swashbuckle.AspNetCore.IntegrationTests/CodeGenerationTests.cs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,21 @@ public static TheoryData<ClientGeneratorTool, string> SnapshotTestCases()
3333

3434
foreach (var tool in Enum.GetValues<ClientGeneratorTool>())
3535
{
36-
if (tool is ClientGeneratorTool.NSwag && Path.GetFileNameWithoutExtension(path).Contains("Basic.Startup"))
36+
var fileName = Path.GetFileNameWithoutExtension(path);
37+
38+
if (fileName.Contains("Basic.Startup"))
3739
{
38-
// NSwag doesn't generate valid compilation due to a missing FileResponse type
39-
continue;
40+
if (tool is ClientGeneratorTool.NSwag)
41+
{
42+
// NSwag doesn't generate valid compilation due to a missing FileResponse type
43+
continue;
44+
}
45+
46+
if (!fileName.Contains("=v1."))
47+
{
48+
// Ignore duplicative test cases for Swagger 2.0 and OpenAPI 3.1
49+
continue;
50+
}
4051
}
4152

4253
if (ClientGenerator.IsSupported(tool, "json", version))

test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerIntegrationTests.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ public class SwaggerIntegrationTests(ITestOutputHelper outputHelper)
1111
{
1212
[Theory]
1313
[InlineData(typeof(Basic.Startup), "/swagger/v1/swagger.json")]
14+
[InlineData(typeof(Basic.Startup), "/swagger/v1/swaggerv2.json")]
15+
[InlineData(typeof(Basic.Startup), "/swagger/v1/swaggerv3_1.json")]
1416
[InlineData(typeof(CliExample.Startup), "/swagger/v1/swagger_net10.0.json")]
1517
[InlineData(typeof(ConfigFromFile.Startup), "/swagger/v1/swagger.json")]
1618
[InlineData(typeof(CustomUIConfig.Startup), "/swagger/v1/swagger.json")]

test/Swashbuckle.AspNetCore.IntegrationTests/VerifyTests.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ public partial class VerifyTests(ITestOutputHelper outputHelper)
1010

1111
[Theory]
1212
[InlineData(typeof(Basic.Startup), "/swagger/v1/swagger.json")]
13+
[InlineData(typeof(Basic.Startup), "/swagger/v1/swaggerv2.json", "2.0")]
14+
[InlineData(typeof(Basic.Startup), "/swagger/v1/swaggerv3_1.json", "3.1")]
1315
[InlineData(typeof(NSwagClientExample.Startup), "/swagger/v1/swagger.json")]
1416
[InlineData(typeof(CliExample.Startup), "/swagger/v1/swagger_net10.0.json")]
1517
[InlineData(typeof(ConfigFromFile.Startup), "/swagger/v1/swagger.json")]
@@ -24,7 +26,8 @@ public partial class VerifyTests(ITestOutputHelper outputHelper)
2426
[InlineData(typeof(TestFirst.Startup), "/swagger/v1-generated/openapi.json")]
2527
public async Task SwaggerEndpoint_ReturnsValidSwaggerJson(
2628
Type startupType,
27-
string swaggerRequestUri)
29+
string swaggerRequestUri,
30+
string openApiVersion = null)
2831
{
2932
var testSite = new TestSite(startupType, outputHelper);
3033
using var client = testSite.BuildClient();
@@ -34,7 +37,7 @@ public async Task SwaggerEndpoint_ReturnsValidSwaggerJson(
3437

3538
await Verify(NormalizeLineBreaks(swagger))
3639
.UseDirectory(SnapshotsDirectory)
37-
.UseParameters(startupType, GetVersion(swaggerRequestUri));
40+
.UseParameters(startupType, openApiVersion ?? GetVersion(swaggerRequestUri));
3841
}
3942

4043
[Fact]

0 commit comments

Comments
 (0)