Skip to content

Commit 9c9568b

Browse files
committed
use one of behind a flag instead for nullable enums
1 parent 29579d8 commit 9c9568b

File tree

15 files changed

+636
-32
lines changed

15 files changed

+636
-32
lines changed

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
4141
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
4242
<UseArtifactsOutput>true</UseArtifactsOutput>
43-
<VersionPrefix>8.1.4</VersionPrefix>
43+
<VersionPrefix>8.2.0</VersionPrefix>
4444
<WarnOnPackingNonPackableProject>false</WarnOnPackingNonPackableProject>
4545
</PropertyGroup>
4646
<PropertyGroup Condition=" '$(GITHUB_ACTIONS)' != '' AND '$(DEPENDABOT_JOB_ID)' == '' ">

src/Swashbuckle.AspNetCore.Newtonsoft/SchemaGenerator/NewtonsoftDataContractResolver.cs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,13 @@ public DataContract GetDataContractForType(Type type)
2222
jsonConverter: JsonConverterFunc);
2323
}
2424

25-
var jsonContract = _contractResolver.ResolveContract(effectiveType);
25+
var jsonContract = _contractResolver.ResolveContract(type);
2626

27-
if (jsonContract is JsonPrimitiveContract && !jsonContract.UnderlyingType.IsEnum)
27+
var effectiveUnderlyingType = Nullable.GetUnderlyingType(jsonContract.UnderlyingType) ?? jsonContract.UnderlyingType;
28+
29+
if (jsonContract is JsonPrimitiveContract && !effectiveUnderlyingType.IsEnum)
2830
{
29-
if (!PrimitiveTypesAndFormats.TryGetValue(jsonContract.UnderlyingType, out var primitiveTypeAndFormat))
31+
if (!PrimitiveTypesAndFormats.TryGetValue(effectiveUnderlyingType, out var primitiveTypeAndFormat))
3032
{
3133
primitiveTypeAndFormat = Tuple.Create(DataType.String, (string)null);
3234
}
@@ -38,9 +40,9 @@ public DataContract GetDataContractForType(Type type)
3840
jsonConverter: JsonConverterFunc);
3941
}
4042

41-
if (jsonContract is JsonPrimitiveContract && jsonContract.UnderlyingType.IsEnum)
43+
if (jsonContract is JsonPrimitiveContract && effectiveUnderlyingType.IsEnum)
4244
{
43-
var enumValues = jsonContract.UnderlyingType.GetEnumValues();
45+
var enumValues = effectiveUnderlyingType.GetEnumValues();
4446

4547
// Test to determine if the serializer will treat as string
4648
var serializeAsString = (enumValues.Length > 0) &&
@@ -52,7 +54,7 @@ public DataContract GetDataContractForType(Type type)
5254

5355
var primitiveTypeAndFormat = serializeAsString
5456
? PrimitiveTypesAndFormats[typeof(string)]
55-
: PrimitiveTypesAndFormats[jsonContract.UnderlyingType.GetEnumUnderlyingType()];
57+
: PrimitiveTypesAndFormats[effectiveUnderlyingType.GetEnumUnderlyingType()];
5658

5759
return DataContract.ForPrimitive(
5860
underlyingType: jsonContract.UnderlyingType,

src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/ConfigureSchemaGeneratorOptions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ private static void DeepCopy(SchemaGeneratorOptions source, SchemaGeneratorOptio
3232
target.DiscriminatorNameSelector = source.DiscriminatorNameSelector;
3333
target.DiscriminatorValueSelector = source.DiscriminatorValueSelector;
3434
target.UseAllOfToExtendReferenceSchemas = source.UseAllOfToExtendReferenceSchemas;
35+
target.UseOneOfForNullableEnums = source.UseOneOfForNullableEnums;
3536
target.SupportNonNullableReferenceTypes = source.SupportNonNullableReferenceTypes;
3637
target.NonNullableReferenceTypesAsRequired = source.NonNullableReferenceTypesAsRequired;
3738
target.SchemaFilters = [.. source.SchemaFilters];

src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/SwaggerGenOptionsExtensions.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,15 @@ public static void UseAllOfToExtendReferenceSchemas(this SwaggerGenOptions swagg
299299
swaggerGenOptions.SchemaGeneratorOptions.UseAllOfToExtendReferenceSchemas = true;
300300
}
301301

302+
/// <summary>
303+
/// Extend enumeration schemas using the <c>oneOf</c> construct to allow <see langword="null"/> when referenced.
304+
/// </summary>
305+
/// <param name="swaggerGenOptions"></param>
306+
public static void UseOneOfForNullableEnums(this SwaggerGenOptions swaggerGenOptions)
307+
{
308+
swaggerGenOptions.SchemaGeneratorOptions.UseOneOfForNullableEnums = true;
309+
}
310+
302311
/// <summary>
303312
/// Enable detection of non nullable reference types to set Nullable flag accordingly on schema properties
304313
/// </summary>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
static Microsoft.Extensions.DependencyInjection.SwaggerGenOptionsExtensions.UseOneOfForNullableEnums(this Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenOptions swaggerGenOptions) -> void
2+
Swashbuckle.AspNetCore.SwaggerGen.SchemaGeneratorOptions.UseOneOfForNullableEnums.get -> bool
3+
Swashbuckle.AspNetCore.SwaggerGen.SchemaGeneratorOptions.UseOneOfForNullableEnums.set -> void

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public DataContract GetDataContractForType(Type type)
4949
primitiveTypeAndFormat = PrimitiveTypesAndFormats[exampleType];
5050

5151
return DataContract.ForPrimitive(
52-
underlyingType: effectiveType,
52+
underlyingType: type,
5353
dataType: primitiveTypeAndFormat.Item1,
5454
dataFormat: primitiveTypeAndFormat.Item2,
5555
jsonConverter: (value) => JsonConverterFunc(value, type));

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

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,18 @@ private OpenApiSchema GenerateSchemaForMember(
5454
MemberInfo memberInfo,
5555
DataProperty dataProperty = null)
5656
{
57+
if (dataProperty != null)
58+
{
59+
var customAttributes = memberInfo.GetInlineAndMetadataAttributes();
60+
61+
var requiredAttribute = customAttributes.OfType<RequiredAttribute>().FirstOrDefault();
62+
63+
if (!IsNullable(requiredAttribute, dataProperty, memberInfo))
64+
{
65+
modelType = Nullable.GetUnderlyingType(modelType) ?? modelType;
66+
}
67+
}
68+
5769
var dataContract = GetDataContractFor(modelType);
5870

5971
var schema = _generatorOptions.UseOneOfForPolymorphism && IsBaseTypeWithKnownTypesDefined(dataContract, out var knownTypesDataContracts)
@@ -143,6 +155,13 @@ private OpenApiSchema GenerateSchemaForParameter(
143155
ParameterInfo parameterInfo,
144156
ApiParameterRouteInfo routeInfo)
145157
{
158+
var customAttributes = parameterInfo.GetCustomAttributes();
159+
160+
if (customAttributes.OfType<RequiredAttribute>().Any())
161+
{
162+
modelType = Nullable.GetUnderlyingType(modelType) ?? modelType;
163+
}
164+
146165
var dataContract = GetDataContractFor(modelType);
147166

148167
var schema = _generatorOptions.UseOneOfForPolymorphism && IsBaseTypeWithKnownTypesDefined(dataContract, out var knownTypesDataContracts)
@@ -157,8 +176,6 @@ private OpenApiSchema GenerateSchemaForParameter(
157176

158177
if (schema.Reference == null)
159178
{
160-
var customAttributes = parameterInfo.GetCustomAttributes();
161-
162179
var defaultValue = parameterInfo.HasDefaultValue
163180
? parameterInfo.DefaultValue
164181
: customAttributes.OfType<DefaultValueAttribute>().FirstOrDefault()?.Value;
@@ -168,6 +185,11 @@ private OpenApiSchema GenerateSchemaForParameter(
168185
schema.Default = GenerateDefaultValue(dataContract, modelType, defaultValue);
169186
}
170187

188+
if (Nullable.GetUnderlyingType(modelType) is not null)
189+
{
190+
schema.Nullable = true;
191+
}
192+
171193
schema.ApplyValidationAttributes(customAttributes);
172194
if (routeInfo != null)
173195
{
@@ -271,7 +293,7 @@ private OpenApiSchema GenerateConcreteSchema(DataContract dataContract, SchemaRe
271293
case DataType.Number:
272294
case DataType.String:
273295
{
274-
schemaFactory = () => CreatePrimitiveSchema(dataContract);
296+
schemaFactory = () => CreatePrimitiveSchema(dataContract, schemaRepository);
275297
returnAsReference = dataContract.UnderlyingType.IsEnum && !_generatorOptions.UseInlineDefinitionsForEnums;
276298
break;
277299
}
@@ -317,8 +339,35 @@ private bool TryGetCustomTypeMapping(Type modelType, out Func<OpenApiSchema> sch
317339
(modelType.IsConstructedGenericType && _generatorOptions.CustomTypeMappings.TryGetValue(modelType.GetGenericTypeDefinition(), out schemaFactory));
318340
}
319341

320-
private static OpenApiSchema CreatePrimitiveSchema(DataContract dataContract)
342+
private OpenApiSchema CreatePrimitiveSchema(DataContract dataContract, SchemaRepository schemaRepository)
321343
{
344+
var underlyingType = Nullable.GetUnderlyingType(dataContract.UnderlyingType) ?? dataContract.UnderlyingType;
345+
346+
if (underlyingType.IsEnum && dataContract.UnderlyingType != underlyingType)
347+
{
348+
var enumDataContract = GetDataContractFor(underlyingType);
349+
350+
var enumSchema = GenerateConcreteSchema(enumDataContract, schemaRepository);
351+
352+
if (_generatorOptions.UseInlineDefinitionsForEnums)
353+
{
354+
enumSchema.Enum.Add(null);
355+
return enumSchema;
356+
}
357+
358+
if (_generatorOptions.UseOneOfForNullableEnums)
359+
{
360+
enumSchema.OneOf =
361+
[
362+
new OpenApiSchema { Reference = enumSchema.Reference },
363+
new OpenApiSchema { Enum = [null] }
364+
];
365+
enumSchema.Reference = null;
366+
}
367+
368+
return enumSchema;
369+
}
370+
322371
var schema = new OpenApiSchema
323372
{
324373
Type = FromDataType(dataContract.DataType),
@@ -338,8 +387,6 @@ private static OpenApiSchema CreatePrimitiveSchema(DataContract dataContract)
338387
}
339388
#pragma warning restore CS0618 // Type or member is obsolete
340389

341-
var underlyingType = dataContract.UnderlyingType;
342-
343390
if (underlyingType.IsEnum)
344391
{
345392
var enumValues = underlyingType.GetEnumValues().Cast<object>();
@@ -451,7 +498,7 @@ private OpenApiSchema CreateObjectSchema(DataContract dataContract, SchemaReposi
451498
continue;
452499
}
453500

454-
var memberType = dataProperty.MemberType;
501+
var memberType = dataProperty.IsNullable ? dataProperty.MemberType : (Nullable.GetUnderlyingType(dataProperty.MemberType) ?? dataProperty.MemberType);
455502

456503
schema.Properties[dataProperty.Name] = (dataProperty.MemberInfo != null)
457504
? GenerateSchemaForMember(memberType, schemaRepository, dataProperty.MemberInfo, dataProperty)

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ public SchemaGeneratorOptions()
4040

4141
public IList<ISchemaFilter> SchemaFilters { get; set; }
4242

43+
/// <summary>
44+
/// Gets or sets a value indicating whether to extend enumeration schemas using the <c>oneOf</c> construct to allow <see langword="null"/> when referenced.
45+
/// </summary>
46+
public bool UseOneOfForNullableEnums { get; set; }
47+
4348
private string DefaultSchemaIdSelector(Type modelType)
4449
{
4550
if (!modelType.IsConstructedGenericType)

src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGenerator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -598,7 +598,7 @@ apiParameter.Type is not null &&
598598

599599
var schema = (type != null)
600600
? GenerateSchema(
601-
type,
601+
Nullable.GetUnderlyingType(type) ?? type,
602602
schemaRepository,
603603
apiParameter.PropertyInfo(),
604604
apiParameter.ParameterInfo(),

0 commit comments

Comments
 (0)