Skip to content

Commit

Permalink
Add SerializerBuilder option WithTargetInvocationExceptionsHandling t…
Browse files Browse the repository at this point in the history
…o support backward compatibility
  • Loading branch information
alexeykuptsov committed Dec 26, 2023
1 parent b31ee5b commit 4d3571f
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 12 deletions.
14 changes: 12 additions & 2 deletions YamlDotNet.Test/Serialization/SerializationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1117,17 +1117,27 @@ public void SerializationEmitsPropertyWhenValueDifferFromDefaultValueAttribute()
}

[Fact]
public void SerializationHandlesThrowingProperties()
public void SerializationHandlesTargetInvocationException()
{
var serializer = new SerializerBuilder().WithTargetInvocationExceptionsHandling().Build();
var writer = new StringWriter();
var obj = new ThrowingPropertyExample();

Serializer.Serialize(writer, obj);
serializer.Serialize(writer, obj);
var serialized = writer.ToString();

serialized.Should()
.Be("Value: Exception of type System.InvalidOperationException was thrown\r\n".NormalizeNewLines());
}

[Fact]
public void SerializationDoesntHandleTargetInvocationExceptionByDefault()
{
var writer = new StringWriter();
var obj = new ThrowingPropertyExample();

Assert.Throws<TargetInvocationException>(() => Serializer.Serialize(writer, obj));
}

[Fact]
public void SerializingAGenericDictionaryShouldNotThrowTargetException()
Expand Down
12 changes: 11 additions & 1 deletion YamlDotNet/Serialization/SerializerBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ public sealed class SerializerBuilder : BuilderSkeleton<SerializerBuilder>
private ScalarStyle defaultScalarStyle = ScalarStyle.Any;
private bool quoteNecessaryStrings;
private bool quoteYaml1_1Strings;
private bool handleTargetInvocationExceptions;

public SerializerBuilder()
: base(new DynamicTypeResolver())
Expand Down Expand Up @@ -120,6 +121,15 @@ public SerializerBuilder WithQuotingNecessaryStrings(bool quoteYaml1_1Strings =
return this;
}

/// <summary>
/// Enables handling TargetInvocationExceptions thrown by a property so that information about exception is serialized as string value of the property.
/// </summary>
public SerializerBuilder WithTargetInvocationExceptionsHandling()
{
handleTargetInvocationExceptions = true;
return this;
}

/// <summary>
/// Sets the default quoting style for scalar values. The default value is <see cref="ScalarStyle.Any"/>
/// </summary>
Expand Down Expand Up @@ -598,7 +608,7 @@ public IValueSerializer BuildValueSerializer()

internal ITypeInspector BuildTypeInspector()
{
ITypeInspector innerInspector = new ReadablePropertiesTypeInspector(typeResolver, includeNonPublicProperties);
ITypeInspector innerInspector = new ReadablePropertiesTypeInspector(typeResolver, includeNonPublicProperties, handleTargetInvocationExceptions);

if (!ignoreFields)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,21 @@ public sealed class ReadablePropertiesTypeInspector : TypeInspectorSkeleton
{
private readonly ITypeResolver typeResolver;
private readonly bool includeNonPublicProperties;
private readonly bool handleTargetInvocationExceptions;

public ReadablePropertiesTypeInspector(ITypeResolver typeResolver)
: this(typeResolver, false)
{
}

public ReadablePropertiesTypeInspector(ITypeResolver typeResolver, bool includeNonPublicProperties)
public ReadablePropertiesTypeInspector(
ITypeResolver typeResolver,
bool includeNonPublicProperties,
bool handleTargetInvocationExceptions = false)
{
this.typeResolver = typeResolver ?? throw new ArgumentNullException(nameof(typeResolver));
this.includeNonPublicProperties = includeNonPublicProperties;
this.handleTargetInvocationExceptions = handleTargetInvocationExceptions;
}

private static bool IsValidProperty(PropertyInfo property)
Expand All @@ -57,18 +62,23 @@ public override IEnumerable<IPropertyDescriptor> GetProperties(Type type, object
return type
.GetProperties(includeNonPublicProperties)
.Where(IsValidProperty)
.Select(p => (IPropertyDescriptor)new ReflectionPropertyDescriptor(p, typeResolver));
.Select(p => (IPropertyDescriptor)new ReflectionPropertyDescriptor(p, typeResolver, handleTargetInvocationExceptions));
}

private sealed class ReflectionPropertyDescriptor : IPropertyDescriptor
{
private readonly PropertyInfo propertyInfo;
private readonly ITypeResolver typeResolver;
private readonly bool handleTargetInvocationExceptions;

public ReflectionPropertyDescriptor(PropertyInfo propertyInfo, ITypeResolver typeResolver)
public ReflectionPropertyDescriptor(
PropertyInfo propertyInfo,
ITypeResolver typeResolver,
bool handleTargetInvocationExceptions)
{
this.propertyInfo = propertyInfo ?? throw new ArgumentNullException(nameof(propertyInfo));
this.typeResolver = typeResolver ?? throw new ArgumentNullException(nameof(typeResolver));
this.handleTargetInvocationExceptions = handleTargetInvocationExceptions;
ScalarStyle = ScalarStyle.Any;
}

Expand All @@ -93,16 +103,24 @@ public void Write(object target, object? value)
public IObjectDescriptor Read(object target)
{
object? propertyValue;
try
if (handleTargetInvocationExceptions)
{
propertyValue = propertyInfo.ReadValue(target);
try
{
propertyValue = propertyInfo.ReadValue(target);
}
catch (TargetInvocationException e)
{
return new ObjectDescriptor(
$"Exception of type {e.InnerException!.GetType().FullName} was thrown",
typeof(string), typeof(string), ScalarStyle.Any);
}
}
catch (TargetInvocationException e)
else
{
return new ObjectDescriptor(
$"Exception of type {e.InnerException!.GetType().FullName} was thrown",
typeof(string), typeof(string), ScalarStyle.Any);
propertyValue = propertyInfo.ReadValue(target);
}

var actualType = TypeOverride ?? typeResolver.Resolve(Type, propertyValue);
return new ObjectDescriptor(propertyValue, actualType, Type, ScalarStyle);
}
Expand Down

0 comments on commit 4d3571f

Please sign in to comment.