Skip to content

Commit bfeb4da

Browse files
authored
Features/ignore data sources (#160)
* Support for ignoring a given source member Tidying * Reusing created variable value * Switching single-method interfaces to delegates * Features/member population context (#159) * Reusing created variable value * Switching single-method interfaces to delegates * Adding MemberPopulationContext * Avoiding capture creation in .Project() * Avoiding capture creation in .Filter() * Avoiding closure creation in .FirstOrDefault() * Avoiding closure creation in .First() * Reusing MemberPopulationContext and DataSourceFindContext throughout complex type member population creation * Start of SourceMemberMatchContext Tidying * Adding reuseable SourceMemberMatchContext * Tidying * Support for conditional member ignores * Erroring if redundant source ignore configured * Support for all-target source member ignores / Test coverage for ignoring a write-only source member * Invalid configuration test coverage * Fixing configured source member ignore conflict detection * Extra test coverage * Extending test coverage * Support for extending source ignores in inline configuration * Test coverage for incorrect inline source ignore configuration * Start of support for ignoring source members by filter * Support for ignoring source members by filter * Erroring if invalid source member filters are specified * Extending test coverage for global source filters * Support for maptime ignoring of source members by derived type * Splitting source member ignore classes * Splitting target member ignore classes * Renaming ignore classes * Moving shared member ignore logic into interface extension methods * Optimising single-value DataSourceSets Renames * Basic source value filter support * Support for global object-value filters * Test coverage for multi-clause global typed source value filter * Extending test coverage * Renames * Support for filtering simple type source enumerable element values * Extending test coverage * Test coverage for filtering a simple type configured data source * Updating test * Not falling back to a default data source if a configured data source has no default matching source member * Optimising source value filter creation * Tidying * Support for maptime-checked derived source type filters * Test coverage for source value filter-ignored runtime typed derived sources * Extending test coverage * Support for inline-configured source value filters and overlapping multiple source value filters * Removing duplicate filter application / Start of handling null nested members in value filter expressions * Support for nested access checking in source value filters * Support for combining conditional data sources with inline source value filters * Erroring if duplicate source value filters are defined * Erroring if empty source filter configured * Extra test coverage * Support for conditional source value filters using the If() syntax * Organising source value filter classes * Adding / updating documentation * Updating release notes
1 parent f646619 commit bfeb4da

File tree

146 files changed

+4973
-1321
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

146 files changed

+4973
-1321
lines changed

AgileMapper.UnitTests.Common/ShouldExtensions.cs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@ public static void ShouldBe<TActual, TExpected>(this TActual value, TExpected ex
5252
{
5353
if (!AreEqual(expectedValue, value))
5454
{
55-
Asplode(expectedValue.ToString(), value?.ToString());
55+
Asplode(
56+
expectedValue?.ToString() ?? (typeof(TExpected).CanBeNull() ? "null" : "default"),
57+
value?.ToString() ?? (typeof(TActual).CanBeNull() ? "null" : "default"));
5658
}
5759
}
5860

@@ -405,14 +407,18 @@ public static IDictionary<TKey, TValue> ShouldContainKeyAndValue<TKey, TValue>(
405407
return dictionary;
406408
}
407409

408-
public static void ShouldBeOfType<TExpected>(this object actual)
410+
public static TExpected ShouldBeOfType<TExpected>(this object actual)
409411
{
410-
if (!(actual is TExpected))
412+
if (actual is TExpected expected)
411413
{
412-
Asplode(
413-
"An object of type " + typeof(TExpected).GetFriendlyName(),
414-
actual.GetType().GetFriendlyName());
414+
return expected;
415415
}
416+
417+
Asplode(
418+
"An object of type " + typeof(TExpected).GetFriendlyName(),
419+
actual.GetType().GetFriendlyName());
420+
421+
return default(TExpected);
416422
}
417423

418424
public static void ShouldContain<T>(this IList<T> actual, T expected)

AgileMapper.UnitTests/AgileMapper.UnitTests.csproj

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@
103103
<Compile Include="Configuration\Inline\WhenConfiguringTypeIdentifiersInline.cs" />
104104
<Compile Include="Configuration\Inline\WhenIgnoringMembersInline.cs" />
105105
<Compile Include="Configuration\Inline\WhenIgnoringMembersInlineIncorrectly.cs" />
106+
<Compile Include="Configuration\Inline\WhenIgnoringSourceMemberInlineIncorrectly.cs" />
107+
<Compile Include="Configuration\Inline\WhenIgnoringSourceMembersByValueFilterInline.cs" />
108+
<Compile Include="Configuration\Inline\WhenIgnoringSourceMembersInline.cs" />
106109
<Compile Include="Configuration\Inline\WhenMappingToNullInline.cs" />
107110
<Compile Include="Configuration\Inline\WhenValidatingMappingsInline.cs" />
108111
<Compile Include="Configuration\Inline\WhenViewingMappingPlans.cs" />
@@ -111,6 +114,12 @@
111114
<Compile Include="Configuration\WhenConfiguringEntityMapping.cs" />
112115
<Compile Include="Configuration\WhenConfiguringReverseDataSources.cs" />
113116
<Compile Include="Configuration\WhenConfiguringReverseDataSourcesIncorrectly.cs" />
117+
<Compile Include="Configuration\WhenIgnoringSourceMembers.cs" />
118+
<Compile Include="Configuration\WhenIgnoringSourceMembersByFilter.cs" />
119+
<Compile Include="Configuration\WhenIgnoringSourceMembersByGlobalFilter.cs" />
120+
<Compile Include="Configuration\WhenIgnoringSourceMembersByValueFilter.cs" />
121+
<Compile Include="Configuration\WhenIgnoringSourceMembersByValueFilterIncorrectly.cs" />
122+
<Compile Include="Configuration\WhenIgnoringSourceMembersIncorrectly.cs" />
114123
<Compile Include="Configuration\WhenResolvingServices.cs" />
115124
<Compile Include="Configuration\WhenViewingMappingPlans.cs" />
116125
<Compile Include="Dictionaries\WhenMappingFromDictionariesOnToComplexTypes.cs" />
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
namespace AgileObjects.AgileMapper.UnitTests.Configuration.Inline
2+
{
3+
using AgileMapper.Configuration;
4+
using Common;
5+
using TestClasses;
6+
#if !NET35
7+
using Xunit;
8+
#else
9+
using Fact = NUnit.Framework.TestAttribute;
10+
11+
[NUnit.Framework.TestFixture]
12+
#endif
13+
public class WhenIgnoringSourceMemberInlineIncorrectly
14+
{
15+
[Fact]
16+
public void ShouldErrorIfDuplicateSourceIgnoreIsConfiguredInline()
17+
{
18+
var ignoreEx = Should.Throw<MappingConfigurationException>(() =>
19+
{
20+
using (var mapper = Mapper.CreateNew())
21+
{
22+
mapper.WhenMapping
23+
.From<PublicField<int>>()
24+
.IgnoreSource(pf => pf.Value);
25+
26+
mapper
27+
.Map(new PublicField<int> { Value = 123 })
28+
.ToANew<PublicField<long>>(cfg => cfg
29+
.IgnoreSource(pf => pf.Value));
30+
}
31+
});
32+
33+
ignoreEx.Message.ShouldContain("has already been ignored");
34+
}
35+
}
36+
}
Lines changed: 301 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
1+
namespace AgileObjects.AgileMapper.UnitTests.Configuration.Inline
2+
{
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using AgileMapper.Extensions.Internal;
7+
using AgileObjects.AgileMapper.Configuration;
8+
using Common;
9+
using TestClasses;
10+
#if !NET35
11+
using Xunit;
12+
#else
13+
using Fact = NUnit.Framework.TestAttribute;
14+
15+
[NUnit.Framework.TestFixture]
16+
#endif
17+
public class WhenIgnoringSourceMembersByValueFilterInline
18+
{
19+
[Fact]
20+
public void ShouldIgnoreSourceValuesByMultiClauseTypedValueFiltersOnline()
21+
{
22+
using (var mapper = Mapper.CreateNew())
23+
{
24+
var matchingIntResult = mapper
25+
.Map(new PublicField<int> { Value = 123 })
26+
.ToANew<PublicProperty<int>>(cfg => cfg
27+
.IgnoreSources(c =>
28+
c.If<string>(str => str == "123") || c.If<int>(i => i == 123) ||
29+
(c.If<string>(str => str != "999") && !c.If<DateTime>(dt => dt == DateTime.Today))));
30+
31+
matchingIntResult.ShouldNotBeNull();
32+
matchingIntResult.Value.ShouldBeDefault();
33+
34+
var matchingStringResult = mapper
35+
.Map(new PublicField<string> { Value = "123" })
36+
.ToANew<PublicProperty<string>>(cfg => cfg
37+
.IgnoreSources(c =>
38+
c.If<string>(str => str == "123") || c.If<int>(i => i == 123) ||
39+
(c.If<string>(str => str != "999") && !c.If<DateTime>(dt => dt == DateTime.Today))));
40+
41+
matchingStringResult.ShouldNotBeNull();
42+
matchingStringResult.Value.ShouldBeNull();
43+
44+
var nonMatchingIntResult = mapper
45+
.Map(new PublicField<int> { Value = 456 })
46+
.ToANew<PublicProperty<int>>(cfg => cfg
47+
.IgnoreSources(c =>
48+
c.If<string>(str => str == "123") || c.If<int>(i => i == 123) ||
49+
(c.If<string>(str => str != "999") && !c.If<DateTime>(dt => dt == DateTime.Today))));
50+
51+
nonMatchingIntResult.ShouldNotBeNull();
52+
nonMatchingIntResult.Value.ShouldBe(456);
53+
54+
var nonMatchingStringResult = mapper
55+
.Map(new PublicField<string> { Value = "999" })
56+
.ToANew<PublicProperty<string>>(cfg => cfg
57+
.IgnoreSources(c =>
58+
c.If<string>(str => str == "123") || c.If<int>(i => i == 123) ||
59+
(c.If<string>(str => str != "999") && !c.If<DateTime>(dt => dt == DateTime.Today))));
60+
61+
nonMatchingStringResult.ShouldNotBeNull();
62+
nonMatchingStringResult.Value.ShouldBe("999");
63+
64+
var nonMatchingTypeResult = mapper
65+
.Map(new PublicField<long> { Value = 123L })
66+
.ToANew<PublicProperty<string>>(cfg => cfg
67+
.IgnoreSources(c =>
68+
c.If<string>(str => str == "123") || c.If<int>(i => i == 123) ||
69+
(c.If<string>(str => str != "999") && !c.If<DateTime>(dt => dt == DateTime.Today))));
70+
71+
nonMatchingTypeResult.ShouldNotBeNull();
72+
nonMatchingTypeResult.Value.ShouldBe("123");
73+
}
74+
}
75+
76+
[Fact]
77+
public void ShouldHandleNullMemberInANestedSourceValueFilterInline()
78+
{
79+
using (var mapper = Mapper.CreateNew())
80+
{
81+
var result = mapper
82+
.Map(new List<Customer>
83+
{
84+
new Customer { Name = "Customer 1", Address = new Address { Line1 = "1 Street" } },
85+
new MysteryCustomer { Name = "Customer 2"}
86+
})
87+
.ToANew<IEnumerable<CustomerViewModel>>(cfg => cfg
88+
.IgnoreSources(s => s.If<Customer>(c => c.Address.Line1.Length < 2)));
89+
90+
result.ShouldNotBeNull();
91+
result.ShouldHaveSingleItem();
92+
result.First().Name.ShouldBe("Customer 1");
93+
result.First().AddressLine1.ShouldBe("1 Street");
94+
}
95+
}
96+
97+
[Fact]
98+
public void ShouldFilterAnEnumerableSourceValueConditionallyInline()
99+
{
100+
using (var mapper = Mapper.CreateNew())
101+
{
102+
mapper.WhenMapping
103+
.From<PublicTwoFields<Product[], Product[]>>()
104+
.Over<PublicProperty<List<ProductDto>>>()
105+
.Map(ctx => ctx.Source.Value1)
106+
.To(t => t.Value)
107+
.But
108+
.If(ctx => ctx.Source.Value1.None())
109+
.Map(ctx => ctx.Source.Value2)
110+
.To(t => t.Value);
111+
112+
var target = new PublicProperty<List<ProductDto>>();
113+
114+
var bothValuesSource = new PublicTwoFields<Product[], Product[]>
115+
{
116+
Value1 = new[] { new Product { ProductId = "111" } },
117+
Value2 = new[] { new Product { ProductId = "222" } }
118+
};
119+
120+
mapper.Map(bothValuesSource).Over(target);
121+
122+
target.Value.ShouldHaveSingleItem().ProductId.ShouldBe("111");
123+
target.Value.Clear();
124+
125+
var emptyValue1Source = new PublicTwoFields<Product[], Product[]>
126+
{
127+
Value1 = Enumerable<Product>.EmptyArray,
128+
Value2 = new[] { new Product { ProductId = "222" } }
129+
};
130+
131+
mapper.Map(emptyValue1Source).Over(target);
132+
133+
target.Value.ShouldHaveSingleItem().ProductId.ShouldBe("222");
134+
target.Value.Clear();
135+
136+
mapper
137+
.Map(bothValuesSource)
138+
.Over(target, cfg => cfg
139+
.IgnoreSources(s => s.If<Product[]>(ps => ps[0].ProductId == "111")));
140+
141+
target.Value.ShouldBeEmpty();
142+
target.Value = null;
143+
144+
mapper
145+
.Map(bothValuesSource)
146+
.Over(target, cfg => cfg
147+
.IgnoreSources(s => s.If<Product[]>(ps => ps[0].ProductId == "111")));
148+
149+
target.Value.ShouldBeNull();
150+
}
151+
}
152+
153+
[Fact]
154+
public void ShouldExtendSourceValueFilterConfiguration()
155+
{
156+
using (var mapper = Mapper.CreateNew())
157+
{
158+
mapper.WhenMapping
159+
.From<PublicTwoFieldsStruct<int, long>>()
160+
.Over<PublicTwoFields<long, long>>()
161+
.IgnoreSources(c => c.If<int>(i => i < 10));
162+
163+
var result1 = mapper
164+
.Map(new PublicTwoFieldsStruct<int, long> { Value1 = 4, Value2 = 12L })
165+
.Over(new PublicTwoFields<long, long>(), cfg => cfg
166+
.IgnoreSources(c => c.If<long>(l => l > 10L)));
167+
168+
result1.Value1.ShouldBeDefault(); // int < 10
169+
result1.Value2.ShouldBeDefault(); // long > 10
170+
171+
var result2 = mapper
172+
.Map(new PublicTwoFieldsStruct<int, long> { Value1 = 20, Value2 = 15L })
173+
.Over(new PublicTwoFields<long, long>(), cfg => cfg
174+
.IgnoreSources(c => c.If<long>(l => l > 10L)));
175+
176+
result2.Value1.ShouldBe(20);
177+
result2.Value2.ShouldBeDefault(); // long > 10
178+
179+
mapper.InlineContexts().ShouldHaveSingleItem();
180+
181+
var result3 = mapper
182+
.Map(new PublicTwoFieldsStruct<int, long> { Value1 = 20, Value2 = 11L })
183+
.Over(new PublicTwoFields<long, long>(), cfg => cfg
184+
.IgnoreSources(c => c.If<int>(i => i < 25))
185+
.And
186+
.IgnoreSources(c => c.If<long>(l => l > 12L)));
187+
188+
result3.Value1.ShouldBeDefault(); // int < 25
189+
result3.Value2.ShouldBe(11);
190+
191+
mapper.InlineContexts().Count.ShouldBe(2);
192+
}
193+
}
194+
195+
[Fact]
196+
public void ShouldReplaceASourceValueFilterWithAConditionalFilterInline()
197+
{
198+
using (var mapper = Mapper.CreateNew())
199+
{
200+
mapper.WhenMapping
201+
.IgnoreSources(c => c.If<int>(value => value % 2 == 0));
202+
203+
var evenValueSource = new PublicField<int> { Value = 8 };
204+
var eventValueResult = mapper.Map(evenValueSource).ToANew<PublicProperty<long>>();
205+
206+
eventValueResult.ShouldNotBeNull();
207+
eventValueResult.Value.ShouldBeDefault();
208+
209+
var oddValueSource = new PublicField<int> { Value = 5 };
210+
var oddValueResult = mapper.Map(oddValueSource).ToANew<PublicProperty<long>>();
211+
212+
oddValueResult.ShouldNotBeNull();
213+
oddValueResult.Value.ShouldBe(5);
214+
215+
var inlineFilterSmallEvenValueResult = mapper
216+
.Map(evenValueSource)
217+
.ToANew<PublicProperty<long>>(cfg => cfg
218+
.If(ctx => ctx.Source.Value > 10)
219+
.IgnoreSources(c => c.If<int>(value => value % 2 == 0)));
220+
221+
inlineFilterSmallEvenValueResult.ShouldNotBeNull();
222+
inlineFilterSmallEvenValueResult.Value.ShouldBe(8);
223+
224+
var inlineFilterLargeEvenValueResult = mapper
225+
.Map(new PublicField<int> { Value = 16 })
226+
.ToANew<PublicProperty<long>>(cfg => cfg
227+
.If(ctx => ctx.Source.Value > 10)
228+
.IgnoreSources(c => c.If<int>(value => value % 2 == 0)));
229+
230+
inlineFilterLargeEvenValueResult.ShouldNotBeNull();
231+
inlineFilterLargeEvenValueResult.Value.ShouldBeDefault();
232+
233+
mapper.InlineContexts().ShouldHaveSingleItem();
234+
}
235+
}
236+
237+
[Fact]
238+
public void ShouldReplaceAMultiClauseSourceValueFilterWithAConditionalFilterInline()
239+
{
240+
using (var mapper = Mapper.CreateNew())
241+
{
242+
mapper.WhenMapping
243+
.IgnoreSources(c => c.If<int>(value => value > 3) && c.If<int>(value => value < 8));
244+
245+
var filteredValueSource = new PublicField<int> { Value = 6 };
246+
var filteredValueResult = mapper.Map(filteredValueSource).ToANew<PublicProperty<long>>();
247+
248+
filteredValueResult.ShouldNotBeNull();
249+
filteredValueResult.Value.ShouldBeDefault();
250+
251+
var unfilteredValueSource = new PublicField<int> { Value = 2 };
252+
var unfilteredValueResult = mapper.Map(unfilteredValueSource).ToANew<PublicProperty<long>>();
253+
254+
unfilteredValueResult.ShouldNotBeNull();
255+
unfilteredValueResult.Value.ShouldBe(2);
256+
257+
var inlineUnfilteredValueResult = mapper
258+
.Map(filteredValueSource)
259+
.ToANew<PublicProperty<long>>(cfg => cfg
260+
.If(ctx => ctx.Source.Value != 6)
261+
.IgnoreSources(c => c.If<int>(value => value > 3) && c.If<int>(value => value < 8)));
262+
263+
inlineUnfilteredValueResult.ShouldNotBeNull();
264+
inlineUnfilteredValueResult.Value.ShouldBe(6);
265+
266+
var inlineFilteredValueResult = mapper
267+
.Map(new PublicField<int> { Value = 7 })
268+
.ToANew<PublicProperty<long>>(cfg => cfg
269+
.If(ctx => ctx.Source.Value != 6)
270+
.IgnoreSources(c => c.If<int>(value => value > 3) && c.If<int>(value => value < 8)));
271+
272+
inlineFilteredValueResult.ShouldNotBeNull();
273+
inlineFilteredValueResult.Value.ShouldBeDefault();
274+
275+
mapper.InlineContexts().ShouldHaveSingleItem();
276+
}
277+
}
278+
279+
[Fact]
280+
public void ShouldErrorIfDuplicateSourceValueFilterConfiguredInline()
281+
{
282+
var configEx = Should.Throw<MappingConfigurationException>(() =>
283+
{
284+
using (var mapper = Mapper.CreateNew())
285+
{
286+
mapper.WhenMapping
287+
.IgnoreSources(c => c.If<int>(value => value == 555));
288+
289+
mapper
290+
.Map(new PublicField<int>())
291+
.ToANew<PublicProperty<long>>(cfg => cfg
292+
.IgnoreSources(c => c.If<int>(value => value == 555)));
293+
}
294+
});
295+
296+
configEx.Message.ShouldContain("Source filter");
297+
configEx.Message.ShouldContain("If<int>(value => value == 555)");
298+
configEx.Message.ShouldContain("already been configured");
299+
}
300+
}
301+
}

0 commit comments

Comments
 (0)