Skip to content

Commit 1b5a85b

Browse files
committed
3.0.0-preview5: Polymorphism, bug fixes, and better exceptions.
1 parent 2b9489a commit 1b5a85b

24 files changed

+610
-378
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public static IServiceCollection AddMappersFrom(this IServiceCollection services
3434
{
3535
services.AddSingleton(types.ServiceType, provider =>
3636
{
37-
var expression = methodGetter(provider.GetRequiredService!);
37+
var expression = methodGetter(provider.GetRequiredService);
3838
return ActivatorUtilities.CreateInstance(provider, types.ImplementationType, expression);
3939
});
4040
});

NotSoAutoMapper.Extensions.Ioc.DryIoc/HandmadeMapperDryIocContainerExtensions.cs renamed to NotSoAutoMapper.Extensions.Ioc.DryIoc/NotSoAutoMapperDryIocContainerExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ public static void RegisterMappersFrom(this IRegistrator registrator, Type type)
105105

106106
NotSoAutoMapperIocContainerUtilities.AddMappersFrom(type, (method, descriptor, getter) => {
107107
registrator.RegisterDelegate(descriptor.ServiceType, resolver => {
108-
var expression = getter(resolver.Resolve!);
108+
var expression = getter(resolver.Resolve);
109109
try
110110
{
111111
return resolver.Resolve(descriptor.ImplementationType, new object[] { expression },

NotSoAutoMapper.Tests/ExpressionProcessing/MapTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public void WithNullMapper_Throws()
5050
{
5151
Expression<Func<object, int>> expression = x => ((IMapper<object, int>) null!).Map(x);
5252

53-
Assert.ThrowsException<InvalidOperationException>(() => expression.ApplyTransformations());
53+
Assert.ThrowsException<ExpressionTransformationException>(() => expression.ApplyTransformations());
5454
}
5555
}
5656
}

NotSoAutoMapper.Tests/ExpressionProcessing/MapWithCollectionOrObjectTests.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,5 +119,19 @@ public void Object_PutsMapperExpression()
119119

120120
Assert.That.ExpressionsAreEqual(expectedExpression, expression);
121121
}
122+
123+
[TestMethod]
124+
public void RootQueryable_HasExpression()
125+
{
126+
var mapperExpression = s_catDtoMapper.Expression;
127+
var baseQueryable = Enumerable.Empty<Cat>().AsQueryable();
128+
129+
var mappedQueryable = baseQueryable.MapWith(s_catDtoMapper);
130+
131+
// EnumerableQuery<Cat>.Select([expression])
132+
var selectQueryableExpression =
133+
((UnaryExpression) ((MethodCallExpression) mappedQueryable.Expression).Arguments[1]).Operand;
134+
Assert.That.ExpressionsAreEqual(mapperExpression, selectQueryableExpression);
135+
}
122136
}
123137
}

NotSoAutoMapper.Tests/MapperEnumerableExtensionsTests.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ private static IMapper<int, int> CreateDefaultMapper()
4343
{
4444
var mapper = Substitute.For<IMapper<int, int>>();
4545
mapper.Expression.Returns(x => x + 5);
46-
mapper.OriginalExpression.Returns(_ => mapper.Expression);
4746
mapper.Map(Arg.Any<int>()).Returns(x => mapper.Expression.Compile().Invoke(x.Arg<int>()));
4847
return mapper;
4948
}
Lines changed: 0 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,12 @@
11
using System;
2-
using System.Collections.Generic;
32
using System.Linq.Expressions;
4-
using NotSoAutoMapper.ExpressionProcessing;
53
using Microsoft.VisualStudio.TestTools.UnitTesting;
64

75
namespace NotSoAutoMapper.Tests
86
{
97
[TestClass]
108
public class MapperTests
119
{
12-
[TestMethod]
13-
public void UseExpression_AlreadyHavingAnException_Throws() => Assert.ThrowsException<InvalidOperationException>(() => new ShouldThrowExceptionTestMapper());
14-
15-
[TestMethod]
16-
public void ExpressionTransformers_AreUsed_Eager()
17-
{
18-
var success = false;
19-
var processor = new TestExpressionTransformer(() => success = true);
20-
_ = new Mapper<object, object>(x => x, new[] {processor});
21-
22-
// Normally, the expression should already have been processed.
23-
24-
Assert.IsTrue(success, "success is false.");
25-
}
26-
2710
[TestMethod]
2811
public void Map_MapsObjectFromExpression()
2912
{
@@ -55,32 +38,5 @@ public void Map_MapsNullToNull()
5538

5639
Assert.IsNull(result);
5740
}
58-
59-
private class ShouldThrowExceptionTestMapper : Mapper<object, object>
60-
{
61-
public ShouldThrowExceptionTestMapper(IEnumerable<IMapperExpressionTransformer>? expressionTransformers = null) :
62-
base(x => new object(), expressionTransformers)
63-
{
64-
UseExpression(x => new object());
65-
}
66-
}
67-
68-
private class TestExpressionTransformer : IMapperExpressionTransformer
69-
{
70-
private readonly Action _onSuccess;
71-
72-
public TestExpressionTransformer(Action onSuccess)
73-
{
74-
_onSuccess = onSuccess;
75-
}
76-
77-
public IMapperExpressionTransformer.RunPosition Position => IMapperExpressionTransformer.RunPosition.Beginning;
78-
79-
public Expression<T> Transform<T>(Expression<T> source)
80-
{
81-
_onSuccess();
82-
return source;
83-
}
84-
}
8541
}
8642
}

NotSoAutoMapper.Tests/MergingExtensionsTests.cs

Lines changed: 2 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
using System.Linq.Expressions;
44
using NotSoAutoMapper.Tests.TestExtensions;
55
using Microsoft.VisualStudio.TestTools.UnitTesting;
6-
using NSubstitute;
7-
using NSubstitute.ReceivedExtensions;
86

97
namespace NotSoAutoMapper.Tests
108
{
@@ -189,41 +187,11 @@ public void Merge_MergesExpressionWithExtension()
189187
Expression<Func<Thing, ThingDto>> expression = x => new ThingDto { Id = x.Id };
190188
Expression<Func<Thing, ThingDto>> extension = x => new ThingDto { Name = x.Name };
191189
var expectedExpression = expression.Merge(extension);
192-
var mapper = CreateMapperSubstitute(expression);
190+
var mapper = new Mapper<Thing, ThingDto>(expression);
193191

194192
var result = mapper.Merge(extension);
195-
196-
_ = mapper.Received(Quantity.AtLeastOne()).Expression; // Only the Expression should get used.
193+
197194
Assert.That.ExpressionsAreEqual(expectedExpression, result.Expression);
198195
}
199-
200-
[TestMethod]
201-
public void MergeOriginal_MergesOriginalExpressionWithExtension()
202-
{
203-
Expression<Func<Thing, ThingDto>> expression = x => new ThingDto();
204-
Expression<Func<Thing, ThingDto>> originalExpression = x => new ThingDto { Id = x.Id };
205-
Expression<Func<Thing, ThingDto>> extension = x => new ThingDto { Name = x.Name };
206-
var expectedExpression = originalExpression.Merge(extension);
207-
var mapper = CreateMapperSubstitute(expression, originalExpression);
208-
209-
var result = mapper.MergeOriginal(extension);
210-
211-
_ = mapper.Received(Quantity.AtLeastOne())
212-
.OriginalExpression; // Only the OriginalExpression should get used.
213-
Assert.That.ExpressionsAreEqual(expectedExpression, result.OriginalExpression);
214-
}
215-
216-
private static IMapper<TInput, TResult> CreateMapperSubstitute<TInput, TResult>(
217-
Expression<Func<TInput, TResult>> expression, Expression<Func<TInput, TResult>>? originalExpression = null)
218-
where TInput : notnull
219-
where TResult : notnull
220-
{
221-
var mapper = Substitute.For<IMapper<TInput, TResult>>();
222-
mapper.OriginalExpression.Returns(originalExpression ?? expression);
223-
mapper.Expression.Returns(expression);
224-
mapper.WithExpression(Arg.Any<Expression<Func<TInput, TResult>>>())
225-
.Returns(call => CreateMapperSubstitute(call.Arg<Expression<Func<TInput, TResult>>>()));
226-
return mapper;
227-
}
228196
}
229197
}

NotSoAutoMapper/AbstractMapper.cs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics.CodeAnalysis;
4+
using System.Linq.Expressions;
5+
using NotSoAutoMapper.ExpressionProcessing;
6+
7+
namespace NotSoAutoMapper
8+
{
9+
/// <summary>
10+
/// The base class for implementations of <see cref="IMapper{TInput, TResult}"/>.
11+
/// </summary>
12+
/// <typeparam name="TInput">The input type the mapper is using.</typeparam>
13+
/// <typeparam name="TResult">The result type the mapper will give.</typeparam>
14+
public abstract class AbstractMapper<TInput, TResult> : IMapper<TInput, TResult>
15+
where TInput : notnull
16+
where TResult : notnull
17+
{
18+
private readonly Lazy<Func<TInput, TResult>> _compiledExpression;
19+
20+
/// <summary>
21+
/// Constructs a new AbstractMapper.
22+
/// </summary>
23+
protected AbstractMapper()
24+
{
25+
_compiledExpression = new Lazy<Func<TInput, TResult>>(() => Expression.Compile());
26+
}
27+
28+
/// <inheritdoc />
29+
public abstract Expression<Func<TInput, TResult>> Expression { get; }
30+
31+
/// <inheritdoc />
32+
[TransformedUsing(typeof(MapExpressionTransformer))]
33+
[return: NotNullIfNotNull("source")]
34+
public TResult? Map(TInput? source)
35+
{
36+
return EqualityComparer<TInput>.Default.Equals(source!, default!)
37+
? default
38+
: _compiledExpression.Value(source!);
39+
}
40+
41+
LambdaExpression IMapper.Expression => Expression;
42+
}
43+
}

NotSoAutoMapper/ExpressionProcessing/ApplyMethodTransformationsVisitor.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,17 @@ internal sealed class ApplyMethodTransformationsVisitor : ExpressionVisitor
1313

1414
protected override Expression VisitMethodCall(MethodCallExpression node)
1515
{
16-
var transformer = s_methodTransformersCache.GetOrAdd(node.Method, ProvideExpressionTransformer);
17-
return transformer is null ? base.VisitMethodCall(node) : transformer.Transform(node);
16+
try
17+
{
18+
var transformer = s_methodTransformersCache.GetOrAdd(node.Method, ProvideExpressionTransformer);
19+
return transformer is null ? base.VisitMethodCall(node) : transformer.Transform(node);
20+
}
21+
catch (Exception e) when (e is not ExpressionTransformationException)
22+
{
23+
var innerMessageEndSentence = string.IsNullOrEmpty(e.Message) ? "." : ". " + e.Message;
24+
throw new ExpressionTransformationException(
25+
$"Failed to transform expression `{node}`{innerMessageEndSentence}", e);
26+
}
1827
}
1928

2029
private static IMethodExpressionTransformer? ProvideExpressionTransformer(MethodInfo method)
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using System;
2+
3+
namespace NotSoAutoMapper.ExpressionProcessing
4+
{
5+
/// <summary>
6+
/// Represents an error during expression transformation.
7+
/// </summary>
8+
public class ExpressionTransformationException : Exception
9+
{
10+
/// <summary>
11+
/// Creates a new <see cref="ExpressionTransformationException"/>.
12+
/// </summary>
13+
public ExpressionTransformationException()
14+
{
15+
}
16+
17+
/// <summary>
18+
/// Creates a new <see cref="ExpressionTransformationException"/> with the given message.
19+
/// </summary>
20+
/// <param name="message">The message.</param>
21+
public ExpressionTransformationException(string message) : base(message)
22+
{
23+
}
24+
25+
/// <summary>
26+
/// Creates a new <see cref="ExpressionTransformationException"/> with the given message and inner exception.
27+
/// </summary>
28+
/// <param name="message">The message.</param>
29+
/// <param name="innerException">The inner exception.</param>
30+
public ExpressionTransformationException(string message, Exception innerException) : base(message, innerException)
31+
{
32+
}
33+
}
34+
}

0 commit comments

Comments
 (0)