Skip to content

Commit

Permalink
Only support static member accessors for interfaces and abstract types
Browse files Browse the repository at this point in the history
  • Loading branch information
Thibaud DESODT committed Nov 23, 2017
1 parent e81439a commit dde160d
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 60 deletions.
61 changes: 33 additions & 28 deletions src/Serilog/Settings/KeyValuePairs/SettingValueConversions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,34 +35,6 @@ class SettingValueConversions

public static object ConvertToType(string value, Type toType)
{
if (TryParseStaticMemberAccessor(value, out var accessorTypeName, out var memberName))
{
var accessorType = Type.GetType(accessorTypeName, throwOnError: true);
var publicStaticPropertyInfo = accessorType.GetTypeInfo().DeclaredProperties
.Where(x => x.Name == memberName)
.Where(x => x.GetMethod != null)
.Where(x => x.GetMethod.IsPublic)
.FirstOrDefault(x => x.GetMethod.IsStatic);

if (publicStaticPropertyInfo != null)
{
return publicStaticPropertyInfo.GetValue(null); // static property, no instance to pass
}

// no property ? look for a field
var publicStaticFieldInfo = accessorType.GetTypeInfo().DeclaredFields
.Where(x => x.Name == memberName)
.Where(x => x.IsPublic)
.FirstOrDefault(x => x.IsStatic);

if (publicStaticFieldInfo != null)
{
return publicStaticFieldInfo.GetValue(null); // static field, no instance to pass
}

throw new InvalidOperationException($"Could not find a public static property or field with name `{memberName}` on type `{accessorTypeName}`");
}

var toTypeInfo = toType.GetTypeInfo();
if (toTypeInfo.IsGenericType && toType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
Expand All @@ -87,6 +59,39 @@ public static object ConvertToType(string value, Type toType)

if ((toTypeInfo.IsInterface || toTypeInfo.IsAbstract) && !string.IsNullOrWhiteSpace(value))
{
// check if value looks like a static property or field directive
// like "Namespace.TypeName::StaticProperty, AssemblyName"
if (TryParseStaticMemberAccessor(value, out var accessorTypeName, out var memberName))
{
var accessorType = Type.GetType(accessorTypeName, throwOnError: true);
// is there a public static property with that name ?
var publicStaticPropertyInfo = accessorType.GetTypeInfo().DeclaredProperties
.Where(x => x.Name == memberName)
.Where(x => x.GetMethod != null)
.Where(x => x.GetMethod.IsPublic)
.FirstOrDefault(x => x.GetMethod.IsStatic);

if (publicStaticPropertyInfo != null)
{
return publicStaticPropertyInfo.GetValue(null); // static property, no instance to pass
}

// no property ? look for a public static field
var publicStaticFieldInfo = accessorType.GetTypeInfo().DeclaredFields
.Where(x => x.Name == memberName)
.Where(x => x.IsPublic)
.FirstOrDefault(x => x.IsStatic);

if (publicStaticFieldInfo != null)
{
return publicStaticFieldInfo.GetValue(null); // static field, no instance to pass
}

throw new InvalidOperationException($"Could not find a public static property or field with name `{memberName}` on type `{accessorTypeName}`");
}

// maybe it's the assembly-qualified type name of a concrete implementation
// with a default constructor
var type = Type.GetType(value.Trim(), throwOnError: false);
if (type != null)
{
Expand Down
38 changes: 14 additions & 24 deletions test/Serilog.Tests/Settings/SettingValueConversionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,13 @@ public void TimeSpanValuesCanBeParsed(string input, int expDays, int expHours, i
[InlineData("Its::a::trapWithColonsAppearingTwice",
null, null)]
[InlineData("ThereIsNoMemberHere::",
null, null)]
null, null)]
[InlineData(null,
null, null)]
null, null)]
[InlineData(" " ,
null, null)]
// a full-qualified type name should not be considered a static member accessor
[InlineData("My.NameSpace.Class, MyAssembly, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
null, null)]
public void TryParseStaticMemberAccessorReturnsExpectedResults(string input, string expectedAccessorType, string expectedPropertyName)
{
Expand All @@ -122,19 +125,6 @@ public void TryParseStaticMemberAccessorReturnsExpectedResults(string input, str
}
}

[Theory]
[InlineData("Serilog.Tests.Support.ClassWithStaticAccessors::StringProperty, Serilog.Tests", typeof(string), "StringProperty")]
[InlineData("Serilog.Tests.Support.ClassWithStaticAccessors::IntProperty, Serilog.Tests", typeof(int), 42)]
[InlineData("Serilog.Tests.Support.ClassWithStaticAccessors::StringField, Serilog.Tests", typeof(string), "StringField")]
[InlineData("Serilog.Tests.Support.ClassWithStaticAccessors::IntField, Serilog.Tests", typeof(int), 666)]
public void StaticMembersAccessorsCanBeUsedForSImpleTypes(string input, Type targetType, object expectedValue)
{
var actual = SettingValueConversions.ConvertToType(input, targetType);

Assert.IsAssignableFrom(targetType, actual);
Assert.Equal(expectedValue, actual);
}

[Theory]
[InlineData("Serilog.Tests.Support.ClassWithStaticAccessors::InterfaceProperty, Serilog.Tests", typeof(IAmAnInterface))]
[InlineData("Serilog.Tests.Support.ClassWithStaticAccessors::AbstractProperty, Serilog.Tests", typeof(AnAbstractClass))]
Expand All @@ -150,12 +140,12 @@ public void StaticMembersAccessorsCanBeUsedForReferenceTypes(string input, Type

[Theory]
// unknown type
[InlineData("Namespace.ThisIsNotAKnownType::StringProperty, Serilog.Tests", typeof(string))]
[InlineData("Namespace.ThisIsNotAKnownType::InterfaceProperty, Serilog.Tests", typeof(IAmAnInterface))]
// good type name, but wrong namespace
[InlineData("Random.Namespace.ClassWithStaticAccessors::StringProperty, Serilog.Tests", typeof(string))]
[InlineData("Random.Namespace.ClassWithStaticAccessors::InterfaceProperty, Serilog.Tests", typeof(IAmAnInterface))]
// good full type name, but missing or wrong assembly
[InlineData("Serilog.Tests.Support.ClassWithStaticAccessors::StringProperty", typeof(string))]
[InlineData("Serilog.Tests.Support.ClassWithStaticAccessors::StringProperty, TestDummies", typeof(string))]
[InlineData("Serilog.Tests.Support.ClassWithStaticAccessors::InterfaceProperty", typeof(IAmAnInterface))]
[InlineData("Serilog.Tests.Support.ClassWithStaticAccessors::InterfaceProperty, TestDummies", typeof(IAmAnInterface))]
public void StaticAccessorOnUnknownTypeThrowsTypeLoadException(string input, Type targetType)
{
Assert.Throws<TypeLoadException>(() =>
Expand All @@ -165,15 +155,15 @@ public void StaticAccessorOnUnknownTypeThrowsTypeLoadException(string input, Typ

[Theory]
// unknown member
[InlineData("Serilog.Tests.Support.ClassWithStaticAccessors::UnknownMember, Serilog.Tests", typeof(string))]
[InlineData("Serilog.Tests.Support.ClassWithStaticAccessors::UnknownMember, Serilog.Tests", typeof(IAmAnInterface))]
// static property exists but it's private
[InlineData("Serilog.Tests.Support.ClassWithStaticAccessors::PrivateStringProperty, Serilog.Tests", typeof(string))]
[InlineData("Serilog.Tests.Support.ClassWithStaticAccessors::PrivateInterfaceProperty, Serilog.Tests", typeof(IAmAnInterface))]
// static field exists but it's private
[InlineData("Serilog.Tests.Support.ClassWithStaticAccessors::PrivateStringField, Serilog.Tests", typeof(string))]
[InlineData("Serilog.Tests.Support.ClassWithStaticAccessors::PrivateInterfaceField, Serilog.Tests", typeof(IAmAnInterface))]
// public property exists but it's not static
[InlineData("Serilog.Tests.Support.ClassWithStaticAccessors::InstanceStringProperty, Serilog.Tests", typeof(string))]
[InlineData("Serilog.Tests.Support.ClassWithStaticAccessors::InstanceInterfaceProperty, Serilog.Tests", typeof(IAmAnInterface))]
// public field exists but it's not static
[InlineData("Serilog.Tests.Support.ClassWithStaticAccessors::InstanceStringField, Serilog.Tests", typeof(string))]
[InlineData("Serilog.Tests.Support.ClassWithStaticAccessors::InstanceInterfaceField, Serilog.Tests", typeof(IAmAnInterface))]
public void StaticAccessorWithInvalidMemberThrowsInvalidOperationException(string input, Type targetType)
{
var exception = Assert.Throws<InvalidOperationException>(() =>
Expand Down
12 changes: 4 additions & 8 deletions test/Serilog.Tests/Support/StaticAccessorClasses.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,19 @@ class ConcreteImpl : AnAbstractClass, IAmAnInterface

public class ClassWithStaticAccessors
{
public static string StringProperty => nameof(StringProperty);
public static int IntProperty => 42;
public static IAmAnInterface InterfaceProperty => ConcreteImpl.Instance;
public static AnAbstractClass AbstractProperty => ConcreteImpl.Instance;

public static string StringField = nameof(StringField);
public static int IntField = 666;
public static IAmAnInterface InterfaceField = ConcreteImpl.Instance;
public static AnAbstractClass AbstractField = ConcreteImpl.Instance;

// ReSharper disable once UnusedMember.Local
static string PrivateStringProperty => nameof(PrivateStringProperty);
static IAmAnInterface PrivateInterfaceProperty => ConcreteImpl.Instance;
#pragma warning disable 169
static string PrivateStringField = nameof(PrivateStringField);
static IAmAnInterface PrivateInterfaceField = ConcreteImpl.Instance;
#pragma warning restore 169
public string InstanceStringProperty => nameof(InstanceStringProperty);
public string InstanceStringField = nameof(InstanceStringField);
public IAmAnInterface InstanceInterfaceProperty => ConcreteImpl.Instance;
public IAmAnInterface InstanceInterfaceField = ConcreteImpl.Instance;

}
}

0 comments on commit dde160d

Please sign in to comment.