diff --git a/src/GalaxyCheck.Tests.V3/DomainGen.cs b/src/GalaxyCheck.Tests.V3/DomainGen.cs index e359e0f5..821cbacb 100644 --- a/src/GalaxyCheck.Tests.V3/DomainGen.cs +++ b/src/GalaxyCheck.Tests.V3/DomainGen.cs @@ -35,7 +35,7 @@ from testFunction in TestFunctions.Nullary() public static Stable.IGen Size() => Stable.Gen.Int32().Between(GalaxyCheck.Gens.Parameters.Size.Zero.Value, GalaxyCheck.Gens.Parameters.Size.Max.Value); - public static Stable.IGen Seed() => Stable.Gen.Int32(); + public static Stable.IGen Seed() => Stable.Gen.Int32().NoShrink(); public static Stable.IGen SeedWaypoint() => Seed().OrNullStruct(); @@ -43,4 +43,9 @@ public static Stable.IGen ToProperty(this Stable.IGen> me from gen in metaGen from testFunction in TestFunctions.Unary() select new Property((IGen>)Property.ForAll(gen, testFunction)); + + public class SeedAttribute : Stable.GenAttribute + { + public override Stable.IGen Value => Seed(); + } } diff --git a/src/GalaxyCheck.Tests.V3/Features/Generators/Operators/AboutWhereOperator.cs b/src/GalaxyCheck.Tests.V3/Features/Generators/Operators/AboutWhereOperator.cs index 602029c2..9fdb520f 100644 --- a/src/GalaxyCheck.Tests.V3/Features/Generators/Operators/AboutWhereOperator.cs +++ b/src/GalaxyCheck.Tests.V3/Features/Generators/Operators/AboutWhereOperator.cs @@ -15,7 +15,7 @@ static void Test(int seed, int size) var filteredGen = baseGen.Where(testFunction); // Act - var sample = filteredGen.Sample(args => args with { Seed = seed, Size = size }); + var sample = filteredGen.SampleTraversal(args => args with { Seed = seed, Size = size }); // Assert sample.Should().OnlyContain(x => testFunction(x)); @@ -35,7 +35,7 @@ static void Test(int seed, int size) var filteredGen = baseGen.Where(testFunction); // Act - var act = () => filteredGen.Sample(args => args with { Seed = seed, Size = size }); + var act = () => filteredGen.SampleTraversal(args => args with { Seed = seed, Size = size }); // Assert act.Should().NotThrow(); diff --git a/src/GalaxyCheck.Tests.V3/Features/Generators/Reflection/AboutDynamicGeneration.cs b/src/GalaxyCheck.Tests.V3/Features/Generators/Reflection/AboutDynamicGeneration.cs index b8c7701f..48249b2a 100644 --- a/src/GalaxyCheck.Tests.V3/Features/Generators/Reflection/AboutDynamicGeneration.cs +++ b/src/GalaxyCheck.Tests.V3/Features/Generators/Reflection/AboutDynamicGeneration.cs @@ -16,7 +16,7 @@ public void ItCanGenerateTheType(Type type) var gen = Gen.Create(type); // Act - var sample = gen.Sample(); + var sample = gen.SampleTraversal(); // Assert sample.Should().AllBeAssignableTo(type); diff --git a/src/GalaxyCheck.Tests.V3/Features/Generators/Reflection/ObjectGraph/AboutErrorMessages.cs b/src/GalaxyCheck.Tests.V3/Features/Generators/Reflection/ObjectGraph/AboutErrorMessages.cs new file mode 100644 index 00000000..76642e4a --- /dev/null +++ b/src/GalaxyCheck.Tests.V3/Features/Generators/Reflection/ObjectGraph/AboutErrorMessages.cs @@ -0,0 +1,37 @@ +namespace GalaxyCheck.Tests.Features.Generators.Reflection.ObjectGraph; + +public class AboutErrorReproduction +{ + [Stable.Property] + [InlineData(typeof(ExampleObjects.UnconstructableClass), "")] + [InlineData(typeof(ClassWithNestedUnconstructableClass), " at path '$.Property'")] + [InlineData(typeof(ClassWithNestedNullableUnconstructableClass), " at path '$.Property'")] + [InlineData(typeof(List), " at path '$.[*]'")] + public Stable.Property WhenTypeIsUnresolvable(Type type, string expectedPathDescription) + { + return Stable.Property.ForAll(DomainGen.Seed(), seed => + { + // Arrange + var gen = Gen.Create(type); + + // Act + var action = () => gen.Sample(args => args with { Seed = seed }); + + // Assert + action + .Should() + .Throw() + .WithGenErrorMessage($"could not resolve type '*'{expectedPathDescription}"); + }); + } + + private class ClassWithNestedUnconstructableClass + { + public ExampleObjects.UnconstructableClass Property { get; set; } = null!; + } + + private class ClassWithNestedNullableUnconstructableClass + { + public ExampleObjects.UnconstructableClass? Property { get; set; } = null!; + } +} diff --git a/src/GalaxyCheck.Tests.V3/Features/Generators/Reflection/ObjectGraph/ExampleObjects.cs b/src/GalaxyCheck.Tests.V3/Features/Generators/Reflection/ObjectGraph/ExampleObjects.cs new file mode 100644 index 00000000..1eeeb2b2 --- /dev/null +++ b/src/GalaxyCheck.Tests.V3/Features/Generators/Reflection/ObjectGraph/ExampleObjects.cs @@ -0,0 +1,69 @@ +namespace GalaxyCheck.Tests.Features.Generators.Reflection.ObjectGraph; + +public static class ExampleObjects +{ + public class ClassWithFailingConstructor + { + private static readonly Exception _exception = new("Constructor failed"); + + public ClassWithFailingConstructor() + { + throw _exception; + } + } + + public class ClassWithNestedFailingConstructor + { + public ClassWithFailingConstructor Property { get; set; } = null!; + } + + public class ClassWithArrayOfFailingConstructors + { + public ClassWithFailingConstructor[] Property { get; set; } = null!; + } + + public class ClassWithArrayOfNestedFailingConstructors + { + public ClassWithNestedFailingConstructor[] Property { get; set; } = null!; + } + + public class ClassWithFailingConstructorAsConstructorParameter + { + public ClassWithFailingConstructor Property { get; set; } + + public ClassWithFailingConstructorAsConstructorParameter(ClassWithFailingConstructor property) + { + Property = property; + } + } + + public class ClassWithNestedFailingConstructorAsConstructorParameter + { + public ClassWithNestedFailingConstructor Property { get; set; } + + public ClassWithNestedFailingConstructorAsConstructorParameter(ClassWithNestedFailingConstructor property) + { + Property = property; + } + } + + public class ClassWithOneProperty + { + public int Property { get; set; } + } + + public class ClassWithTwoProperties + { + public int Property1 { get; set; } + public int Property2 { get; set; } + } + + public class ClassWithOneNestedProperty + { + public ClassWithOneProperty Property { get; set; } = null!; + } + + public abstract class UnconstructableClass + { + } +} diff --git a/src/GalaxyCheck.Tests.V3/GalaxyCheck.Tests.V3.csproj b/src/GalaxyCheck.Tests.V3/GalaxyCheck.Tests.V3.csproj index bf7c2f5e..beb5d345 100644 --- a/src/GalaxyCheck.Tests.V3/GalaxyCheck.Tests.V3.csproj +++ b/src/GalaxyCheck.Tests.V3/GalaxyCheck.Tests.V3.csproj @@ -12,7 +12,7 @@ - + @@ -31,8 +31,4 @@ - - - - diff --git a/src/GalaxyCheck.Tests.V3/TestProxy.cs b/src/GalaxyCheck.Tests.V3/TestProxy.cs index 9c0a6f4d..456a5712 100644 --- a/src/GalaxyCheck.Tests.V3/TestProxy.cs +++ b/src/GalaxyCheck.Tests.V3/TestProxy.cs @@ -75,7 +75,7 @@ public static CheckResult Check(this PropertyProxy propertyProxy, Func - public static IReadOnlyCollection Sample(this IGen gen, Func? configure = null) + public static IReadOnlyCollection SampleTraversal(this IGen gen, Func? configure = null) { var args = SampleGenArgs.Default; if (configure != null) @@ -84,12 +84,26 @@ public static IReadOnlyCollection Sample(this IGen gen, Func Sample(this IGen gen, Func? configure = null) + { + var args = SampleGenArgs.Default; + if (configure != null) + { + args = configure(args); + } + + return gen + .Sample(seed: args.Seed, size: args.Size ?? 100) + .Take(args.MaxSampleSize) + .ToList(); + } + public record CheckPropertyArgs(int Seed, int? Size, string? Replay) { public static CheckPropertyArgs Default { get; } = new(0, null, null); diff --git a/src/GalaxyCheck.Tests.V3/TestUtility/AssertionExtensions.cs b/src/GalaxyCheck.Tests.V3/TestUtility/AssertionExtensions.cs new file mode 100644 index 00000000..26b20ef7 --- /dev/null +++ b/src/GalaxyCheck.Tests.V3/TestUtility/AssertionExtensions.cs @@ -0,0 +1,13 @@ +using FluentAssertions.Specialized; + +namespace GalaxyCheck.Tests.TestUtility; + +public static class AssertionExtensions +{ + public static ExceptionAssertions WithGenErrorMessage( + this ExceptionAssertions assertions, + string messageBody) + { + return assertions.WithMessage("Error during generation: " + messageBody); + } +} diff --git a/src/GalaxyCheck/Gens/FactoryGen.cs b/src/GalaxyCheck/Gens/FactoryGen.cs index 170ffcdf..5284aff0 100644 --- a/src/GalaxyCheck/Gens/FactoryGen.cs +++ b/src/GalaxyCheck/Gens/FactoryGen.cs @@ -229,7 +229,7 @@ protected override IGen Get var context = ReflectedGenHandlerContext.Create(typeof(T), NullabilityInfo); return ReflectedGenBuilder - .Build(typeof(T), RegisteredGensByType, MemberOverrides, Error, context) + .Build(typeof(T), RegisteredGensByType, MemberOverrides, context) .Cast(); } } diff --git a/src/GalaxyCheck/Gens/ReflectedGenHelpers/ErrorFactory.cs b/src/GalaxyCheck/Gens/ReflectedGenHelpers/ErrorFactory.cs index a1cd49ae..4a1a7c3a 100644 --- a/src/GalaxyCheck/Gens/ReflectedGenHelpers/ErrorFactory.cs +++ b/src/GalaxyCheck/Gens/ReflectedGenHelpers/ErrorFactory.cs @@ -1,6 +1,6 @@ -namespace GalaxyCheck.Gens.ReflectedGenHelpers +using System; + +namespace GalaxyCheck.Gens.ReflectedGenHelpers { internal delegate IGen ErrorFactory(string message); - - internal delegate IGen ContextualErrorFactory(string message, ReflectedGenHandlerContext context); } diff --git a/src/GalaxyCheck/Gens/ReflectedGenHelpers/ReflectedGenBuilder.cs b/src/GalaxyCheck/Gens/ReflectedGenHelpers/ReflectedGenBuilder.cs index d5a22edb..18384af6 100644 --- a/src/GalaxyCheck/Gens/ReflectedGenHelpers/ReflectedGenBuilder.cs +++ b/src/GalaxyCheck/Gens/ReflectedGenHelpers/ReflectedGenBuilder.cs @@ -11,30 +11,30 @@ public static IGen Build( Type type, IReadOnlyDictionary> registeredGensByType, IReadOnlyList memberOverrides, - ErrorFactory errorFactory, ReflectedGenHandlerContext context) { - ContextualErrorFactory contextualErrorFactory = (message, context) => - { - var suffix = context.Members.Count() == 1 ? "" : $" at path '{context.MemberPath}'"; - return errorFactory(message + suffix); - }; - var genFactoriesByPriority = new List { new MemberOverrideReflectedGenHandler(memberOverrides), new NullableGenHandler(), - new RegistryReflectedGenHandler(registeredGensByType, contextualErrorFactory), + new RegistryReflectedGenHandler(registeredGensByType), new CollectionReflectedGenHandler(), new ArrayReflectedGenHandler(), new EnumReflectedGenHandler(), - new DefaultConstructorReflectedGenHandler(contextualErrorFactory), - new NonDefaultConstructorReflectedGenHandler(contextualErrorFactory), + new DefaultConstructorReflectedGenHandler(), + new NonDefaultConstructorReflectedGenHandler(), }; - var compositeReflectedGenFactory = new CompositeReflectedGenHandler(genFactoriesByPriority, contextualErrorFactory); + var compositeReflectedGenFactory = new CompositeReflectedGenHandler(genFactoriesByPriority); - return compositeReflectedGenFactory.CreateGen(type, context); + try + { + return compositeReflectedGenFactory.CreateGen(type, context); + } + catch (Exception ex) + { + return Gen.Advanced.Error(type, $"{ex.Message} \n {ex.StackTrace}"); + } } } } diff --git a/src/GalaxyCheck/Gens/ReflectedGenHelpers/ReflectedGenHandlerContext.cs b/src/GalaxyCheck/Gens/ReflectedGenHelpers/ReflectedGenHandlerContext.cs index 130b8dd0..3c1acfaa 100644 --- a/src/GalaxyCheck/Gens/ReflectedGenHelpers/ReflectedGenHandlerContext.cs +++ b/src/GalaxyCheck/Gens/ReflectedGenHelpers/ReflectedGenHandlerContext.cs @@ -36,5 +36,17 @@ public static int CalculateStableSeed(this ReflectedGenHandlerContext context) = return acc + curr; } }); + + public static IGen Error(this ReflectedGenHandlerContext context, string message) + { + var suffix = context.Members.Count == 1 ? "" : $" at path '{context.MemberPath}'"; + return Gen.Advanced.Error(message + suffix); + } + + public static IGen Error(this ReflectedGenHandlerContext context, Type type, string message) + { + var suffix = context.Members.Count == 1 ? "" : $" at path '{context.MemberPath}'"; + return Gen.Advanced.Error(type, message + suffix); + } } } diff --git a/src/GalaxyCheck/Gens/ReflectedGenHelpers/ReflectedGenHandlers/CompositeReflectedGenHandler.cs b/src/GalaxyCheck/Gens/ReflectedGenHelpers/ReflectedGenHandlers/CompositeReflectedGenHandler.cs index 44b2ee7e..ff6662fc 100644 --- a/src/GalaxyCheck/Gens/ReflectedGenHelpers/ReflectedGenHandlers/CompositeReflectedGenHandler.cs +++ b/src/GalaxyCheck/Gens/ReflectedGenHelpers/ReflectedGenHandlers/CompositeReflectedGenHandler.cs @@ -7,14 +7,10 @@ namespace GalaxyCheck.Gens.ReflectedGenHelpers.ReflectedGenHandlers internal class CompositeReflectedGenHandler : IReflectedGenHandler { private readonly IReadOnlyList _genHandlersByPriority; - private readonly ContextualErrorFactory _errorFactory; - public CompositeReflectedGenHandler( - IReadOnlyList genHandlersByPriority, - ContextualErrorFactory errorFactory) + public CompositeReflectedGenHandler(IReadOnlyList genHandlersByPriority) { _genHandlersByPriority = genHandlersByPriority; - _errorFactory = errorFactory; } public bool CanHandleGen(Type type, ReflectedGenHandlerContext context) => @@ -24,7 +20,7 @@ public IGen CreateGen(IReflectedGenHandler innerHandler, Type type, ReflectedGen { if (context.TypeHistory.Skip(1).Any(t => t == type)) { - return _errorFactory($"detected circular reference on type '{type}'", context); + return context.Error(type, $"detected circular reference on type '{type}'"); } var gen = _genHandlersByPriority @@ -34,7 +30,7 @@ public IGen CreateGen(IReflectedGenHandler innerHandler, Type type, ReflectedGen if (gen == null) { - return _errorFactory($"could not resolve type '{type}'", context); + return context.Error(type, $"could not resolve type '{type}'"); } return gen; diff --git a/src/GalaxyCheck/Gens/ReflectedGenHelpers/ReflectedGenHandlers/DefaultConstructorReflectedGenHandler.cs b/src/GalaxyCheck/Gens/ReflectedGenHelpers/ReflectedGenHandlers/DefaultConstructorReflectedGenHandler.cs index 9ad5506a..9c13406b 100644 --- a/src/GalaxyCheck/Gens/ReflectedGenHelpers/ReflectedGenHandlers/DefaultConstructorReflectedGenHandler.cs +++ b/src/GalaxyCheck/Gens/ReflectedGenHelpers/ReflectedGenHandlers/DefaultConstructorReflectedGenHandler.cs @@ -7,13 +7,6 @@ namespace GalaxyCheck.Gens.ReflectedGenHelpers.ReflectedGenHandlers { internal class DefaultConstructorReflectedGenHandler : IReflectedGenHandler { - private readonly ContextualErrorFactory _errorFactory; - - public DefaultConstructorReflectedGenHandler(ContextualErrorFactory errorFactory) - { - _errorFactory = errorFactory; - } - public bool CanHandleGen(Type type, ReflectedGenHandlerContext context) { if (IsStruct(type)) @@ -37,10 +30,10 @@ public IGen CreateGen(IReflectedGenHandler innerHandler, Type type, ReflectedGen var genericMethodInfo = methodInfo.MakeGenericMethod(type); - return (IGen)genericMethodInfo.Invoke(null!, new object[] { innerHandler, context, _errorFactory })!; + return (IGen)genericMethodInfo.Invoke(null!, new object[] { innerHandler, context })!; } - private static IGen CreateGenGeneric(IReflectedGenHandler innerHandler, ReflectedGenHandlerContext context, ContextualErrorFactory errorFactory) + private static IGen CreateGenGeneric(IReflectedGenHandler innerHandler, ReflectedGenHandlerContext context) { return Gen .Zip( @@ -57,7 +50,7 @@ private static IGen CreateGenGeneric(IReflectedGenHandler innerHandler, Re { var innerEx = ex.InnerException; var message = $"'{innerEx!.GetType()}' was thrown while calling constructor with message '{innerEx.Message}'"; - return errorFactory(message, context).Cast(); + return context.Error(message); } foreach (var setPropertyAction in x.Item1) @@ -70,7 +63,7 @@ private static IGen CreateGenGeneric(IReflectedGenHandler innerHandler, Re { var innerEx = ex.InnerException; var message = $"'{innerEx!.GetType()}' was thrown while setting property with message '{innerEx.Message}'"; - return errorFactory(message, context).Cast(); + return context.Error(message); } } @@ -82,10 +75,11 @@ private static IGen CreateGenGeneric(IReflectedGenHandler innerHandler, Re return Gen.Constant((T)instance); }); } - + private delegate void SetMemberAction(ref object instance); - private static IEnumerable> CreateSetPropertyActionGens(IReflectedGenHandler innerHandler, Type type, ReflectedGenHandlerContext parentContext) + private static IEnumerable> CreateSetPropertyActionGens(IReflectedGenHandler innerHandler, Type type, + ReflectedGenHandlerContext parentContext) { var nullabilityInfoContext = new NullabilityInfoContext(); return type @@ -102,7 +96,8 @@ private static IEnumerable> CreateSetPropertyActionGens(IR }); } - private static IEnumerable> CreateSetFieldActionGens(IReflectedGenHandler innerHandler, Type type, ReflectedGenHandlerContext parentContext) + private static IEnumerable> CreateSetFieldActionGens(IReflectedGenHandler innerHandler, Type type, + ReflectedGenHandlerContext parentContext) { var nullabilityInfoContext = new NullabilityInfoContext(); return type diff --git a/src/GalaxyCheck/Gens/ReflectedGenHelpers/ReflectedGenHandlers/NonDefaultConstructorReflectedGenHandler.cs b/src/GalaxyCheck/Gens/ReflectedGenHelpers/ReflectedGenHandlers/NonDefaultConstructorReflectedGenHandler.cs index 4ca4dcd1..173176db 100644 --- a/src/GalaxyCheck/Gens/ReflectedGenHelpers/ReflectedGenHandlers/NonDefaultConstructorReflectedGenHandler.cs +++ b/src/GalaxyCheck/Gens/ReflectedGenHelpers/ReflectedGenHandlers/NonDefaultConstructorReflectedGenHandler.cs @@ -6,13 +6,6 @@ namespace GalaxyCheck.Gens.ReflectedGenHelpers.ReflectedGenHandlers { internal class NonDefaultConstructorReflectedGenHandler : IReflectedGenHandler { - private readonly ContextualErrorFactory _errorFactory; - - public NonDefaultConstructorReflectedGenHandler(ContextualErrorFactory errorFactory) - { - _errorFactory = errorFactory; - } - public bool CanHandleGen(Type type, ReflectedGenHandlerContext context) => TryFindConstructor(type) != null; @@ -24,10 +17,10 @@ public IGen CreateGen(IReflectedGenHandler innerHandler, Type type, ReflectedGen var genericMethodInfo = methodInfo.MakeGenericMethod(type); - return (IGen)genericMethodInfo.Invoke(null!, new object[] { innerHandler, context, _errorFactory })!; + return (IGen)genericMethodInfo.Invoke(null!, new object[] { innerHandler, context })!; } - private static IGen CreateGenGeneric(IReflectedGenHandler innerHandler, ReflectedGenHandlerContext constructorContext, ContextualErrorFactory errorFactory) + private static IGen CreateGenGeneric(IReflectedGenHandler innerHandler, ReflectedGenHandlerContext constructorContext) { var nullabilityInfoContext = new NullabilityInfoContext(); var constructor = TryFindConstructor(typeof(T))!; @@ -37,7 +30,9 @@ private static IGen CreateGenGeneric(IReflectedGenHandler innerHandler, Re .Select(parameter => { // TODO: Indicate it's a ctor param in the path - var parameterContext = constructorContext.Next(parameter.Name ?? "", parameter.ParameterType, nullabilityInfoContext.Create(parameter)); + var parameterContext = constructorContext.Next(parameter.Name ?? "", parameter.ParameterType, + nullabilityInfoContext.Create(parameter)); + return innerHandler .CreateGen(parameter.ParameterType, parameterContext) .Cast() @@ -57,7 +52,7 @@ private static IGen CreateGenGeneric(IReflectedGenHandler innerHandler, Re { var innerEx = ex.InnerException; var message = $"'{innerEx!.GetType()}' was thrown while calling constructor with message '{innerEx.Message}'"; - return errorFactory(message, constructorContext).Cast(); + return constructorContext.Error(message); } }); } diff --git a/src/GalaxyCheck/Gens/ReflectedGenHelpers/ReflectedGenHandlers/RegistryReflectedGenHandler.cs b/src/GalaxyCheck/Gens/ReflectedGenHelpers/ReflectedGenHandlers/RegistryReflectedGenHandler.cs index 69a9a105..90c52279 100644 --- a/src/GalaxyCheck/Gens/ReflectedGenHelpers/ReflectedGenHandlers/RegistryReflectedGenHandler.cs +++ b/src/GalaxyCheck/Gens/ReflectedGenHelpers/ReflectedGenHandlers/RegistryReflectedGenHandler.cs @@ -1,21 +1,16 @@ using GalaxyCheck.Internal; using System; using System.Collections.Generic; -using System.Linq; namespace GalaxyCheck.Gens.ReflectedGenHelpers.ReflectedGenHandlers { internal class RegistryReflectedGenHandler : IReflectedGenHandler { private readonly IReadOnlyDictionary> _registeredGensByType; - private readonly ContextualErrorFactory _errorFactory; - public RegistryReflectedGenHandler( - IReadOnlyDictionary> registeredGensByType, - ContextualErrorFactory errorFactory) + public RegistryReflectedGenHandler(IReadOnlyDictionary> registeredGensByType) { _registeredGensByType = registeredGensByType; - _errorFactory = errorFactory; } public bool CanHandleGen(Type type, ReflectedGenHandlerContext context) => @@ -28,9 +23,7 @@ public IGen CreateGen(IReflectedGenHandler innerHandler, Type type, ReflectedGen var genTypeArgument = gen.ReflectGenTypeArgument(); if (type.IsAssignableFrom(genTypeArgument) == false) { - return _errorFactory( - $"type '{genTypeArgument}' was not assignable to the type it was registered to, '{type}'", - context); + return context.Error(type, $"type '{genTypeArgument}' was not assignable to the type it was registered to, '{type}'"); } return gen; diff --git a/src/GalaxyCheck/Properties/Reflect.cs b/src/GalaxyCheck/Properties/Reflect.cs index 7fba63df..6189f4dc 100644 --- a/src/GalaxyCheck/Properties/Reflect.cs +++ b/src/GalaxyCheck/Properties/Reflect.cs @@ -24,11 +24,14 @@ public static Property Reflect( var message = $"Return type is not supported by {nameof(Property)}.{nameof(Reflect)}."; if (AsyncMethodPropertyHandlers.ContainsKey(methodInfo.ReturnType)) { - message += $" Did you mean to use {nameof(Property)}.{nameof(ReflectAsync)}? Otherwise, please use one of: {supportedReturnTypesFormatted}."; + message += + $" Did you mean to use {nameof(Property)}.{nameof(ReflectAsync)}? Otherwise, please use one of: {supportedReturnTypesFormatted}."; } - else { + else + { message += $" Please use one of: {supportedReturnTypesFormatted}."; } + message += $" Return type was: {methodInfo.ReturnType}."; throw new Exception(message); @@ -48,8 +51,10 @@ public static AsyncProperty ReflectAsync( { if (AsyncMethodPropertyHandlers.TryGetValue(methodInfo.ReturnType, out var asyncHandler) == false) { - var supportedReturnTypesFormatted = FormatSupportReturnTypes(Enumerable.Concat(MethodPropertyHandlers.Keys, AsyncMethodPropertyHandlers.Keys)); - var message = $"Return type is not supported by {nameof(Property)}.{nameof(ReflectAsync)}. Please use one of: {supportedReturnTypesFormatted}. Return type was: {methodInfo.ReturnType}."; + var supportedReturnTypesFormatted = + FormatSupportReturnTypes(Enumerable.Concat(MethodPropertyHandlers.Keys, AsyncMethodPropertyHandlers.Keys)); + var message = + $"Return type is not supported by {nameof(Property)}.{nameof(ReflectAsync)}. Please use one of: {supportedReturnTypesFormatted}. Return type was: {methodInfo.ReturnType}."; throw new Exception(message); } @@ -112,14 +117,11 @@ private static AsyncReflectedPropertyHandler ToAsyncPropertyHandler(ReflectedPro .Parameters(methodInfo, genFactory, customGens) .ForAll(parameters => { - try + SafeInvoke(() => { methodInfo.Invoke(target, parameters); - } - catch (TargetInvocationException ex) - { - throw ex.InnerException!; - } + return 0; + }); })); }; @@ -144,7 +146,8 @@ private static AsyncReflectedPropertyHandler ToAsyncPropertyHandler(ReflectedPro { if (methodInfo.GetParameters().Any() && controlData is null) { - throw new Exception($"Parameters are not support for methods returning properties, unless control data is injected. Violating signature: \"{methodInfo}\""); + throw new Exception( + $"Parameters are not support for methods returning properties, unless control data is injected. Violating signature: \"{methodInfo}\""); } try @@ -195,7 +198,8 @@ private static AsyncReflectedPropertyHandler ToAsyncPropertyHandler(ReflectedPro { if (methodInfo.GetParameters().Any() && controlData is null) { - throw new Exception($"Parameters are not support for methods returning properties, unless control data is injected. Violating signature: \"{methodInfo}\""); + throw new Exception( + $"Parameters are not support for methods returning properties, unless control data is injected. Violating signature: \"{methodInfo}\""); } try @@ -207,5 +211,33 @@ private static AsyncReflectedPropertyHandler ToAsyncPropertyHandler(ReflectedPro throw ex.InnerException!; } }; + + private static T SafeInvoke(Func invoke) + { + try + { + return invoke(); + } + catch (TargetInvocationException ex) + { + throw Unwrap(ex); + } + } + + private static Exception Unwrap(TargetInvocationException ex) + { + var nextEx = ex.InnerException; + if (nextEx is null) + { + return ex; + } + + if (nextEx is TargetInvocationException nextTargetInvocationEx) + { + return Unwrap(nextTargetInvocationEx); + } + + return nextEx; + } } }