From 76ec39d2db343566086e29247970ca7f59017eb7 Mon Sep 17 00:00:00 2001 From: stb-co <93922970+stb-co@users.noreply.github.com> Date: Thu, 16 May 2024 14:47:23 +0200 Subject: [PATCH] Fix schema generation for C# 9 positional record with no example (#2901) * Add schema filter test for when example tag is not present * Add datetime to schema filter example tag positive test * Fix missing example property on record xmldoc param tag causing unexpected empty example string in generated schema --- .../XmlComments/XmlCommentsSchemaFilter.cs | 5 +- .../Fixtures/XmlAnnotatedRecord.cs | 4 +- .../XmlAnnotatedRecordWithoutExample.cs | 35 +++++++++ .../Fixtures/XmlAnnotatedType.cs | 6 ++ .../XmlAnnotatedTypeWithoutExample.cs | 71 +++++++++++++++++++ .../XmlCommentsSchemaFilterTests.cs | 42 +++++++++++ 6 files changed, 161 insertions(+), 2 deletions(-) create mode 100644 test/Swashbuckle.AspNetCore.SwaggerGen.Test/Fixtures/XmlAnnotatedRecordWithoutExample.cs create mode 100644 test/Swashbuckle.AspNetCore.SwaggerGen.Test/Fixtures/XmlAnnotatedTypeWithoutExample.cs diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/XmlCommentsSchemaFilter.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/XmlCommentsSchemaFilter.cs index 4075034e27..a246657d2c 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/XmlCommentsSchemaFilter.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/XmlCommentsSchemaFilter.cs @@ -50,7 +50,10 @@ private void ApplyMemberTags(OpenApiSchema schema, SchemaFilterContext context) schema.Description = XmlCommentsTextHelper.Humanize(summaryNode); var example = recordDefaultConstructorProperty.GetAttribute("example", string.Empty); - TrySetExample(schema, context, example); + if (!string.IsNullOrEmpty(example)) + { + TrySetExample(schema, context, example); + } } if (fieldOrPropertyNode != null) diff --git a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Fixtures/XmlAnnotatedRecord.cs b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Fixtures/XmlAnnotatedRecord.cs index 7b35dbc335..c337c379ed 100644 --- a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Fixtures/XmlAnnotatedRecord.cs +++ b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Fixtures/XmlAnnotatedRecord.cs @@ -4,13 +4,14 @@ namespace Swashbuckle.AspNetCore.SwaggerGen.Test { /// - /// Summary for XmlAnnotatedType + /// Summary for XmlAnnotatedRecord /// /// Summary for BoolProperty /// Summary for IntProperty /// Summary for LongProperty /// Summary for FloatProperty /// Summary for DoubleProperty + /// Summary for DateTimeProperty /// Summary for EnumProperty /// Summary for GuidProperty /// Summary for Nullable StringPropertyWithNullExample @@ -23,6 +24,7 @@ public record XmlAnnotatedRecord( long LongProperty, float FloatProperty, double DoubleProperty, + DateTime DateTimeProperty, IntEnum EnumProperty, Guid GuidProperty, string StringPropertyWithNullExample, diff --git a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Fixtures/XmlAnnotatedRecordWithoutExample.cs b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Fixtures/XmlAnnotatedRecordWithoutExample.cs new file mode 100644 index 0000000000..2eea51841e --- /dev/null +++ b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Fixtures/XmlAnnotatedRecordWithoutExample.cs @@ -0,0 +1,35 @@ +using System; +using Swashbuckle.AspNetCore.TestSupport; + +namespace Swashbuckle.AspNetCore.SwaggerGen.Test +{ + /// + /// Summary for XmlAnnotatedRecordWithoutExample + /// + /// Summary for BoolProperty + /// Summary for IntProperty + /// Summary for LongProperty + /// Summary for FloatProperty + /// Summary for DoubleProperty + /// Summary for DateTimeProperty + /// Summary for EnumProperty + /// Summary for GuidProperty + /// Summary for Nullable StringPropertyWithNullExample + /// Summary for StringProperty + /// Summary for StringPropertyWithUri + /// Summary for ObjectProperty + public record XmlAnnotatedRecordWithoutExample( + bool BoolProperty, + int IntProperty, + long LongProperty, + float FloatProperty, + double DoubleProperty, + DateTime DateTimeProperty, + IntEnum EnumProperty, + Guid GuidProperty, + string StringPropertyWithNullExample, + string StringProperty, + string StringPropertyWithUri, + object ObjectProperty + ); +} \ No newline at end of file diff --git a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Fixtures/XmlAnnotatedType.cs b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Fixtures/XmlAnnotatedType.cs index a61b54bd72..4227d35ffd 100644 --- a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Fixtures/XmlAnnotatedType.cs +++ b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Fixtures/XmlAnnotatedType.cs @@ -45,6 +45,12 @@ public class XmlAnnotatedType /// 1.25 public double DoubleProperty { get; set; } + /// + /// Summary for DateTimeProperty + /// + /// 6/22/2022 12:00:00 AM + public DateTime DateTimeProperty { get; set; } + /// /// Summary for EnumProperty /// diff --git a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Fixtures/XmlAnnotatedTypeWithoutExample.cs b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Fixtures/XmlAnnotatedTypeWithoutExample.cs new file mode 100644 index 0000000000..f06f01e840 --- /dev/null +++ b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Fixtures/XmlAnnotatedTypeWithoutExample.cs @@ -0,0 +1,71 @@ +using System; +using Swashbuckle.AspNetCore.TestSupport; + +namespace Swashbuckle.AspNetCore.SwaggerGen.Test +{ + /// + /// Summary for XmlAnnotatedTypeWithoutExample + /// + public class XmlAnnotatedTypeWithoutExample + { + /// + /// Summary for BoolProperty + /// + public bool BoolProperty { get; set; } + + /// + /// Summary for IntProperty + /// + public int IntProperty { get; set; } + + /// + /// Summary for LongProperty + /// + public long LongProperty { get; set; } + + /// + /// Summary for FloatProperty + /// + public float FloatProperty { get; set; } + + /// + /// Summary for DoubleProperty + /// + public double DoubleProperty { get; set; } + + /// + /// Summary for DateTimeProperty + /// + public DateTime DateTimeProperty { get; set; } + + /// + /// Summary for EnumProperty + /// + public IntEnum EnumProperty { get; set; } + + /// + /// Summary for GuidProperty + /// + public Guid GuidProperty { get; set; } + + /// + /// Summary for Nullable StringPropertyWithNullExample + /// + public string StringPropertyWithNullExample { get; set; } + + /// + /// Summary for StringProperty + /// + public string StringProperty { get; set; } + + /// + /// Summary for StringPropertyWithUri + /// + public string StringPropertyWithUri { get; set; } + + /// + /// Summary for ObjectProperty + /// + public object ObjectProperty { get; set; } + } +} \ No newline at end of file diff --git a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/XmlComments/XmlCommentsSchemaFilterTests.cs b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/XmlComments/XmlCommentsSchemaFilterTests.cs index ecfba3e080..36c32db543 100644 --- a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/XmlComments/XmlCommentsSchemaFilterTests.cs +++ b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/XmlComments/XmlCommentsSchemaFilterTests.cs @@ -62,6 +62,7 @@ public void Apply_SetsDescription_FromPropertySummaryTag( [InlineData(typeof(XmlAnnotatedType), nameof(XmlAnnotatedType.LongProperty), "integer", "4294967295")] [InlineData(typeof(XmlAnnotatedType), nameof(XmlAnnotatedType.FloatProperty), "number", "1.2")] [InlineData(typeof(XmlAnnotatedType), nameof(XmlAnnotatedType.DoubleProperty), "number", "1.25")] + [InlineData(typeof(XmlAnnotatedType), nameof(XmlAnnotatedType.DateTimeProperty), "string", "\"6/22/2022 12:00:00 AM\"")] [InlineData(typeof(XmlAnnotatedType), nameof(XmlAnnotatedType.EnumProperty), "integer", "2")] [InlineData(typeof(XmlAnnotatedType), nameof(XmlAnnotatedType.GuidProperty), "string", "\"d3966535-2637-48fa-b911-e3c27405ee09\"")] [InlineData(typeof(XmlAnnotatedType), nameof(XmlAnnotatedType.StringProperty), "string", "\"Example for StringProperty\"")] @@ -73,6 +74,7 @@ public void Apply_SetsDescription_FromPropertySummaryTag( [InlineData(typeof(XmlAnnotatedRecord), nameof(XmlAnnotatedRecord.LongProperty), "integer", "4294967295")] [InlineData(typeof(XmlAnnotatedRecord), nameof(XmlAnnotatedRecord.FloatProperty), "number", "1.2")] [InlineData(typeof(XmlAnnotatedRecord), nameof(XmlAnnotatedRecord.DoubleProperty), "number", "1.25")] + [InlineData(typeof(XmlAnnotatedRecord), nameof(XmlAnnotatedRecord.DateTimeProperty), "string", "\"6/22/2022 12:00:00 AM\"")] [InlineData(typeof(XmlAnnotatedRecord), nameof(XmlAnnotatedRecord.EnumProperty), "integer", "2")] [InlineData(typeof(XmlAnnotatedRecord), nameof(XmlAnnotatedRecord.GuidProperty), "string", "\"d3966535-2637-48fa-b911-e3c27405ee09\"")] [InlineData(typeof(XmlAnnotatedRecord), nameof(XmlAnnotatedRecord.StringProperty), "string", "\"Example for StringProperty\"")] @@ -96,6 +98,46 @@ public void Apply_SetsExample_FromPropertyExampleTag( Assert.Equal(expectedExampleAsJson, schema.Example.ToJson()); } + [Theory] + [InlineData(typeof(XmlAnnotatedTypeWithoutExample), nameof(XmlAnnotatedTypeWithoutExample.BoolProperty), "boolean")] + [InlineData(typeof(XmlAnnotatedTypeWithoutExample), nameof(XmlAnnotatedTypeWithoutExample.IntProperty), "integer")] + [InlineData(typeof(XmlAnnotatedTypeWithoutExample), nameof(XmlAnnotatedTypeWithoutExample.LongProperty), "integer")] + [InlineData(typeof(XmlAnnotatedTypeWithoutExample), nameof(XmlAnnotatedTypeWithoutExample.FloatProperty), "number")] + [InlineData(typeof(XmlAnnotatedTypeWithoutExample), nameof(XmlAnnotatedTypeWithoutExample.DoubleProperty), "number")] + [InlineData(typeof(XmlAnnotatedTypeWithoutExample), nameof(XmlAnnotatedTypeWithoutExample.DateTimeProperty), "string")] + [InlineData(typeof(XmlAnnotatedTypeWithoutExample), nameof(XmlAnnotatedTypeWithoutExample.EnumProperty), "integer")] + [InlineData(typeof(XmlAnnotatedTypeWithoutExample), nameof(XmlAnnotatedTypeWithoutExample.GuidProperty), "string")] + [InlineData(typeof(XmlAnnotatedTypeWithoutExample), nameof(XmlAnnotatedTypeWithoutExample.StringProperty), "string")] + [InlineData(typeof(XmlAnnotatedTypeWithoutExample), nameof(XmlAnnotatedTypeWithoutExample.ObjectProperty), "object")] + [InlineData(typeof(XmlAnnotatedTypeWithoutExample), nameof(XmlAnnotatedTypeWithoutExample.StringPropertyWithNullExample), "string")] + [InlineData(typeof(XmlAnnotatedTypeWithoutExample), nameof(XmlAnnotatedTypeWithoutExample.StringPropertyWithUri), "string")] + [InlineData(typeof(XmlAnnotatedRecordWithoutExample), nameof(XmlAnnotatedRecordWithoutExample.BoolProperty), "boolean")] + [InlineData(typeof(XmlAnnotatedRecordWithoutExample), nameof(XmlAnnotatedRecordWithoutExample.IntProperty), "integer")] + [InlineData(typeof(XmlAnnotatedRecordWithoutExample), nameof(XmlAnnotatedRecordWithoutExample.LongProperty), "integer")] + [InlineData(typeof(XmlAnnotatedRecordWithoutExample), nameof(XmlAnnotatedRecordWithoutExample.FloatProperty), "number")] + [InlineData(typeof(XmlAnnotatedRecordWithoutExample), nameof(XmlAnnotatedRecordWithoutExample.DoubleProperty), "number")] + [InlineData(typeof(XmlAnnotatedRecordWithoutExample), nameof(XmlAnnotatedRecordWithoutExample.DateTimeProperty), "string")] + [InlineData(typeof(XmlAnnotatedRecordWithoutExample), nameof(XmlAnnotatedRecordWithoutExample.EnumProperty), "integer")] + [InlineData(typeof(XmlAnnotatedRecordWithoutExample), nameof(XmlAnnotatedRecordWithoutExample.GuidProperty), "string")] + [InlineData(typeof(XmlAnnotatedRecordWithoutExample), nameof(XmlAnnotatedRecordWithoutExample.StringProperty), "string")] + [InlineData(typeof(XmlAnnotatedRecordWithoutExample), nameof(XmlAnnotatedRecordWithoutExample.ObjectProperty), "object")] + [InlineData(typeof(XmlAnnotatedRecordWithoutExample), nameof(XmlAnnotatedRecordWithoutExample.StringPropertyWithNullExample), "string")] + [InlineData(typeof(XmlAnnotatedRecordWithoutExample), nameof(XmlAnnotatedRecordWithoutExample.StringPropertyWithUri), "string")] + [UseInvariantCulture] + public void Apply_DoesNotSetExample_WhenPropertyExampleTagIsNotProvided( + Type declaringType, + string propertyName, + string schemaType) + { + var propertyInfo = declaringType.GetProperty(propertyName); + var schema = new OpenApiSchema { Type = schemaType }; + var filterContext = new SchemaFilterContext(propertyInfo.PropertyType, null, null, memberInfo: propertyInfo); + + Subject().Apply(schema, filterContext); + + Assert.Null(schema.Example); + } + [Theory] [InlineData("en-US", 1.2F)] [InlineData("sv-SE", 1.2F)]