-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Fix generation when [DefaultValue]'s type differs #2895
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -41,12 +41,19 @@ public DataContract GetDataContractForType(Type type) | |
| var enumValues = type.GetEnumValues(); | ||
|
|
||
| // Test to determine if the serializer will treat as string | ||
| var serializeAsString = (enumValues.Length > 0) | ||
| && JsonConverterFunc(enumValues.GetValue(0), type).StartsWith("\""); | ||
| var serializeAsString = | ||
| enumValues.Length > 0 && | ||
| #if NET5_0_OR_GREATER | ||
| JsonConverterFunc(enumValues.GetValue(0), type).StartsWith('\"'); | ||
| #else | ||
| JsonConverterFunc(enumValues.GetValue(0), type).StartsWith("\""); | ||
| #endif | ||
|
|
||
| var exampleType = serializeAsString ? | ||
| typeof(string) : | ||
| type.GetEnumUnderlyingType(); | ||
|
|
||
| primitiveTypeAndFormat = serializeAsString | ||
| ? PrimitiveTypesAndFormats[typeof(string)] | ||
| : PrimitiveTypesAndFormats[type.GetEnumUnderlyingType()]; | ||
| primitiveTypeAndFormat = PrimitiveTypesAndFormats[exampleType]; | ||
|
|
||
| return DataContract.ForPrimitive( | ||
| underlyingType: type, | ||
|
|
@@ -144,7 +151,7 @@ public bool IsSupportedCollection(Type type, out Type itemType) | |
| return false; | ||
| } | ||
|
|
||
| private IEnumerable<DataProperty> GetDataPropertiesFor(Type objectType, out Type extensionDataType) | ||
domaindrivendev marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| private List<DataProperty> GetDataPropertiesFor(Type objectType, out Type extensionDataType) | ||
| { | ||
| extensionDataType = null; | ||
|
|
||
|
|
@@ -177,7 +184,7 @@ private IEnumerable<DataProperty> GetDataPropertiesFor(Type objectType, out Type | |
|
|
||
| return | ||
| (property.IsPubliclyReadable() || property.IsPubliclyWritable()) && | ||
| !(property.GetIndexParameters().Any()) && | ||
| !(property.GetIndexParameters().Length > 0) && | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @martincostello is this another performance-related suggestion? Personally, I prefer the readability of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Most likely yes. See CA1860 (Note: I also don't like that rule. I've disabled it in my projects since I don't write high performance code.) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah this is a new analyser rule related to performance as well. |
||
| !(property.HasAttribute<JsonIgnoreAttribute>() && isIgnoredViaNet5Attribute) && | ||
| !(property.HasAttribute<SwaggerIgnoreAttribute>()) && | ||
| !(_serializerOptions.IgnoreReadOnlyProperties && !property.IsPubliclyWritable()); | ||
|
|
||
domaindrivendev marked this conversation as resolved.
Show resolved
Hide resolved
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,6 +10,7 @@ | |
| using Microsoft.AspNetCore.Http; | ||
| using Microsoft.AspNetCore.Mvc; | ||
| using Microsoft.AspNetCore.Mvc.ApiExplorer; | ||
| using Microsoft.OpenApi.Any; | ||
| using Microsoft.OpenApi.Models; | ||
|
|
||
| namespace Swashbuckle.AspNetCore.SwaggerGen | ||
|
|
@@ -55,7 +56,7 @@ private OpenApiSchema GenerateSchemaForMember( | |
|
|
||
| if (_generatorOptions.UseAllOfToExtendReferenceSchemas && schema.Reference != null) | ||
| { | ||
| schema.AllOf = new[] { new OpenApiSchema { Reference = schema.Reference } }; | ||
| schema.AllOf = [new OpenApiSchema { Reference = schema.Reference }]; | ||
| schema.Reference = null; | ||
| } | ||
|
|
||
|
|
@@ -80,8 +81,7 @@ private OpenApiSchema GenerateSchemaForMember( | |
| var defaultValueAttribute = customAttributes.OfType<DefaultValueAttribute>().FirstOrDefault(); | ||
| if (defaultValueAttribute != null) | ||
| { | ||
| var defaultAsJson = dataContract.JsonConverter(defaultValueAttribute.Value); | ||
| schema.Default = OpenApiAnyFactory.CreateFromJson(defaultAsJson); | ||
| schema.Default = GenerateDefaultValue(dataContract, modelType, defaultValueAttribute.Value); | ||
| } | ||
|
|
||
| var obsoleteAttribute = customAttributes.OfType<ObsoleteAttribute>().FirstOrDefault(); | ||
|
|
@@ -90,8 +90,9 @@ private OpenApiSchema GenerateSchemaForMember( | |
| schema.Deprecated = true; | ||
| } | ||
|
|
||
| // NullableAttribute behaves diffrently for Dictionaries | ||
| if (schema.AdditionalPropertiesAllowed && modelType.IsGenericType && modelType.GetGenericTypeDefinition() == typeof(Dictionary<,>)) | ||
| // NullableAttribute behaves differently for Dictionaries | ||
| if (schema.AdditionalPropertiesAllowed && modelType.IsGenericType && | ||
| modelType.GetGenericTypeDefinition() == typeof(Dictionary<,>)) | ||
| { | ||
| schema.AdditionalProperties.Nullable = !memberInfo.IsDictionaryValueNonNullable(); | ||
| } | ||
|
|
@@ -118,7 +119,7 @@ private OpenApiSchema GenerateSchemaForParameter( | |
|
|
||
| if (_generatorOptions.UseAllOfToExtendReferenceSchemas && schema.Reference != null) | ||
| { | ||
| schema.AllOf = new[] { new OpenApiSchema { Reference = schema.Reference } }; | ||
| schema.AllOf = [new OpenApiSchema { Reference = schema.Reference }]; | ||
| schema.Reference = null; | ||
| } | ||
|
|
||
|
|
@@ -132,8 +133,7 @@ private OpenApiSchema GenerateSchemaForParameter( | |
|
|
||
| if (defaultValue != null) | ||
| { | ||
| var defaultAsJson = dataContract.JsonConverter(defaultValue); | ||
| schema.Default = OpenApiAnyFactory.CreateFromJson(defaultAsJson); | ||
| schema.Default = GenerateDefaultValue(dataContract, modelType, defaultValue); | ||
| } | ||
|
|
||
| schema.ApplyValidationAttributes(customAttributes); | ||
|
|
@@ -200,15 +200,15 @@ private OpenApiSchema GeneratePolymorphicSchema( | |
| }; | ||
| } | ||
|
|
||
| private static readonly Type[] BinaryStringTypes = new[] | ||
| { | ||
| private static readonly Type[] BinaryStringTypes = | ||
| [ | ||
| typeof(IFormFile), | ||
| typeof(FileResult), | ||
| typeof(System.IO.Stream), | ||
| #if NETCOREAPP3_0_OR_GREATER | ||
| typeof(System.IO.Pipelines.PipeReader), | ||
| #endif | ||
| }; | ||
| ]; | ||
|
|
||
| private OpenApiSchema GenerateConcreteSchema(DataContract dataContract, SchemaRepository schemaRepository) | ||
| { | ||
|
|
@@ -277,7 +277,7 @@ private bool TryGetCustomTypeMapping(Type modelType, out Func<OpenApiSchema> sch | |
| || (modelType.IsConstructedGenericType && _generatorOptions.CustomTypeMappings.TryGetValue(modelType.GetGenericTypeDefinition(), out schemaFactory)); | ||
| } | ||
|
|
||
| private OpenApiSchema CreatePrimitiveSchema(DataContract dataContract) | ||
| private static OpenApiSchema CreatePrimitiveSchema(DataContract dataContract) | ||
| { | ||
| var schema = new OpenApiSchema | ||
| { | ||
|
|
@@ -292,7 +292,7 @@ private OpenApiSchema CreatePrimitiveSchema(DataContract dataContract) | |
| schema.Enum = dataContract.EnumValues | ||
| .Select(value => JsonSerializer.Serialize(value)) | ||
| .Distinct() | ||
| .Select(valueAsJson => OpenApiAnyFactory.CreateFromJson(valueAsJson)) | ||
| .Select(OpenApiAnyFactory.CreateFromJson) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👌 |
||
| .ToList(); | ||
|
|
||
| return schema; | ||
|
|
@@ -402,7 +402,7 @@ private OpenApiSchema CreateObjectSchema(DataContract dataContract, SchemaReposi | |
|
|
||
| foreach (var dataProperty in applicableDataProperties) | ||
| { | ||
| var customAttributes = dataProperty.MemberInfo?.GetInlineAndMetadataAttributes() ?? Enumerable.Empty<object>(); | ||
| var customAttributes = dataProperty.MemberInfo?.GetInlineAndMetadataAttributes() ?? []; | ||
|
|
||
| if (_generatorOptions.IgnoreObsoleteProperties && customAttributes.OfType<ObsoleteAttribute>().Any()) | ||
| continue; | ||
|
|
@@ -519,5 +519,24 @@ private void ApplyFilters( | |
| filter.Apply(schema, filterContext); | ||
| } | ||
| } | ||
|
|
||
| private IOpenApiAny GenerateDefaultValue( | ||
| DataContract dataContract, | ||
| Type modelType, | ||
| object defaultValue) | ||
| { | ||
| // If the types do not match (e.g. a default which is an integer is specified for a double), | ||
| // attempt to coerce the default value to the correct type so that it can be serialized correctly. | ||
| // See https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/2885 and | ||
| // https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/2904. | ||
| var defaultValueType = defaultValue?.GetType(); | ||
| if (defaultValueType != null && defaultValueType != modelType) | ||
| { | ||
| dataContract = GetDataContractFor(defaultValueType); | ||
| } | ||
|
|
||
| var defaultAsJson = dataContract.JsonConverter(defaultValue); | ||
| return OpenApiAnyFactory.CreateFromJson(defaultAsJson); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk.Web"> | ||
|
|
||
| <PropertyGroup> | ||
| <ImplicitUsings>enable</ImplicitUsings> | ||
| <NoWarn>$(NoWarn);CA1050</NoWarn> | ||
| <Nullable>enable</Nullable> | ||
| <RootNamespace>WebApi</RootNamespace> | ||
| <TargetFramework>net8.0</TargetFramework> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <ProjectReference Include="..\..\..\src\Swashbuckle.AspNetCore.SwaggerGen\Swashbuckle.AspNetCore.SwaggerGen.csproj" /> | ||
| <ProjectReference Include="..\..\..\src\Swashbuckle.AspNetCore.SwaggerUI\Swashbuckle.AspNetCore.SwaggerUI.csproj" /> | ||
| </ItemGroup> | ||
|
|
||
| <ItemGroup> | ||
| <PackageReference Include="Microsoft.AspNetCore.OpenApi" /> | ||
| </ItemGroup> | ||
|
|
||
| </Project> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| using Microsoft.AspNetCore.Mvc; | ||
|
|
||
| var builder = WebApplication.CreateBuilder(args); | ||
|
|
||
| builder.Services.AddControllers(); | ||
|
|
||
| builder.Services.AddEndpointsApiExplorer(); | ||
| builder.Services.AddSwaggerGen(options => | ||
| { | ||
| options.UseAllOfToExtendReferenceSchemas(); | ||
| }); | ||
|
|
||
| var app = builder.Build(); | ||
| app.UseHttpsRedirection(); | ||
|
|
||
| if (app.Environment.IsDevelopment()) | ||
| { | ||
| _ = app.UseSwagger(); | ||
| _ = app.UseSwaggerUI(); | ||
| } | ||
|
|
||
| app.MapControllers(); | ||
|
|
||
| app.Run(); | ||
|
|
||
| [ApiController] | ||
| [Route("api/[controller]")] | ||
| public class EnumController : ControllerBase | ||
| { | ||
| [HttpGet] | ||
| public IActionResult Get(LogLevel? logLevel = LogLevel.Error) => Ok(new { logLevel }); | ||
| } | ||
|
|
||
| namespace MvcWithNullable | ||
| { | ||
| public partial class Program | ||
| { | ||
| // Expose the Program class for use with WebApplicationFactory<T> | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.