Skip to content

Instantly share code, notes, and snippets.

@gaboe
Created September 2, 2020 08:30
Show Gist options
  • Save gaboe/fa93f8f42175cf2407ec53f9ab139e8e to your computer and use it in GitHub Desktop.
Save gaboe/fa93f8f42175cf2407ec53f9ab139e8e to your computer and use it in GitHub Desktop.
NullReferenceTypePropertyFilter
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