Created
September 2, 2020 08:30
-
-
Save gaboe/fa93f8f42175cf2407ec53f9ab139e8e to your computer and use it in GitHub Desktop.
NullReferenceTypePropertyFilter
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class NullReferenceTypePropertyFilter : ISchemaFilter | |
{ | |
public void Apply(OpenApiSchema schema, SchemaFilterContext context) | |
{ | |
Type type; | |
List<CustomAttributeData> attributes; | |
if (context.MemberInfo is PropertyInfo propertyInfo) | |
{ | |
type = propertyInfo.PropertyType; | |
attributes = CollectContextAttributes(propertyInfo.DeclaringType) | |
.Concat(propertyInfo.CustomAttributes) | |
.ToList(); | |
} | |
else if (context.MemberInfo is FieldInfo fieldInfo) | |
{ | |
type = fieldInfo.FieldType; | |
attributes = CollectContextAttributes(fieldInfo.DeclaringType) | |
.Concat(fieldInfo.CustomAttributes) | |
.ToList(); | |
} | |
else if (context.ParameterInfo is var parameterInfo && parameterInfo != null) | |
{ | |
type = parameterInfo.ParameterType; | |
attributes = CollectContextAttributes(parameterInfo.Member.DeclaringType) | |
.Concat(parameterInfo.Member.CustomAttributes) | |
.Concat(parameterInfo.CustomAttributes) | |
.ToList(); | |
} | |
else | |
{ | |
// Propagate Nullable => Required to parent types. | |
foreach (var (property, propertySchema) in schema.Properties) | |
{ | |
if (!propertySchema.Nullable) | |
{ | |
schema.Required.Add(property); | |
} | |
} | |
return; | |
} | |
var valueNullable = | |
type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(Nullable<>)); | |
if (valueNullable) | |
{ | |
schema.Nullable = true; | |
return; | |
} | |
var nullableFlag = GetNullableFlagByAttribute(attributes); | |
if (nullableFlag != 0) | |
{ | |
schema.Nullable = nullableFlag == 2; | |
} | |
if (type.IsValueType) | |
{ | |
schema.Nullable = false; | |
} | |
} | |
private IEnumerable<CustomAttributeData> CollectContextAttributes(Type? declaringType) | |
{ | |
if (declaringType == null) | |
{ | |
return Enumerable.Empty<CustomAttributeData>(); | |
} | |
var attributes = declaringType | |
.CustomAttributes | |
.Where(x => x.AttributeType.FullName == "System.Runtime.CompilerServices.NullableContextAttribute"); | |
return attributes.Concat(CollectContextAttributes(declaringType.DeclaringType)); | |
} | |
/// <remarks> | |
/// https://github.com/dotnet/roslyn/blob/7bc44488c661fd6bbb6c53f39512a6fe0cc5ef84/docs/features/nullable-metadata.md | |
/// </remarks> | |
private static int GetNullableFlagByAttribute(List<CustomAttributeData> attributes) | |
{ | |
for (var i = attributes.Count - 1; i >= 0; i--) | |
{ | |
var attribute = attributes[i]; | |
var fullName = attribute.AttributeType.FullName; | |
if (fullName == null) | |
{ | |
continue; | |
} | |
if (fullName.Contains( | |
"System.Runtime.CompilerServices.NullableAttribute", | |
StringComparison.InvariantCulture)) | |
{ | |
var arg = attribute.ConstructorArguments[0]; | |
if (arg.ArgumentType == typeof(byte)) | |
{ | |
return (byte)arg.Value!; | |
} | |
try | |
{ | |
return ((byte[])arg.Value!)[0]; | |
} | |
catch (Exception e) | |
{ | |
return 0; | |
} | |
} | |
if (fullName.Contains( | |
"System.Runtime.CompilerServices.NullableContextAttribute", | |
StringComparison.InvariantCulture)) | |
{ | |
var nullableFlag = (byte)attribute.ConstructorArguments[0].Value!; | |
return nullableFlag; | |
} | |
} | |
return 0; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment