Skip to content

Commit f4760a5

Browse files
HenkKinHenk Kin
and
Henk Kin
authored
Added Custom mapping (#31)
* Partially incorrect reverse map when numerical values of source and destination enums differ #30 Setup testcase with error * Added Mapping by custom mappings * Added testcases * Updated documentation * Cleanup --------- Co-authored-by: Henk Kin <[email protected]>
1 parent 9b5e191 commit f4760a5

File tree

6 files changed

+326
-21
lines changed

6 files changed

+326
-21
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ For method `CreateMap` this library provide a `ConvertUsingEnumMapping` method.
3737

3838
If you want to change some mappings, then you can use `MapValue` method. This is a chainable method.
3939

40-
Default the enum values are mapped by value, but it is possible to map by name calling `MapByName()` or `MapByValue()`.
40+
Default the enum values are mapped by value (`MapByValue()`), but it is possible to map by name calling `MapByName()`. For enums which does not have same values and names, you can use `MapByCustom()`. Then you have to add a `MapValue` for every source enum value.
4141

4242
```csharp
4343
using AutoMapper.Extensions.EnumMapping;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
using System;
2+
using System.Reflection;
3+
using AutoMapper.Extensions.EnumMapping.Tests.Internal;
4+
using Shouldly;
5+
using Xunit;
6+
7+
namespace AutoMapper.Extensions.EnumMapping.Tests
8+
{
9+
/// <summary>
10+
/// Based on issue #30
11+
/// </summary>
12+
public class ReverseCustomEnumMappingByCustom
13+
{
14+
public class Valid : AutoMapperSpecBase
15+
{
16+
Destination _result;
17+
18+
// Assume, as is the case for my use case, that both the source
19+
// and destination enumerations are created via code generation
20+
// from sources outside our control, and thus I cannot make updates
21+
// or changes to them to work around the issue.
22+
23+
// This is idiomatic of how a Protobuf enum is generated, with the Unspecified
24+
// value acting as a stand-in for when the field is not set by the client or server
25+
public enum Source
26+
{
27+
Unspecified = 0,
28+
Bar = 1,
29+
Baz = 2,
30+
}
31+
32+
// In our case, this is generated from an OpenAPI spec via Kiota
33+
public enum Destination
34+
{
35+
BAR_ALT_NAME, // we can't map by name because the names don't match, even with case insensitivity turned on
36+
BAZ_ALT_NAME,
37+
}
38+
39+
public class TestEnumProfile : Profile
40+
{
41+
public TestEnumProfile()
42+
{
43+
CreateMap<Source, Destination>()
44+
.ConvertUsingEnumMapping(
45+
opts =>
46+
{
47+
opts
48+
.MapByCustom()
49+
.MapValue(Source.Bar, Destination.BAR_ALT_NAME)
50+
.MapValue(Source.Baz, Destination.BAZ_ALT_NAME)
51+
.MapException(Source.Unspecified, () => new InvalidOperationException($"Unspecified values are not supported"));
52+
})
53+
.ReverseMap();
54+
}
55+
}
56+
57+
protected override MapperConfiguration Configuration { get; } = new MapperConfiguration(cfg =>
58+
{
59+
cfg.EnableEnumMappingValidation();
60+
cfg.AddMaps(typeof(ReverseCustomEnumMappingByCustom).GetTypeInfo().Assembly);
61+
});
62+
63+
protected override void Because_of()
64+
{
65+
_result = Mapper.Map<Source, Destination>(Source.Bar);
66+
}
67+
68+
[Fact]
69+
public void Should_map_enum_by_value()
70+
{
71+
_result.ShouldBe(Destination.BAR_ALT_NAME);
72+
}
73+
74+
[Fact]
75+
public void TestBarMapping()
76+
{
77+
// Passes
78+
var res = Mapper.Map<Destination>(Source.Bar);
79+
res.ShouldBe(Destination.BAR_ALT_NAME);
80+
}
81+
82+
[Fact]
83+
public void TestBazMapping()
84+
{
85+
// Passes
86+
var res = Mapper.Map<Destination>(Source.Baz);
87+
res.ShouldBe(Destination.BAZ_ALT_NAME);
88+
}
89+
90+
[Fact]
91+
public void TestUnspecifiedMapping()
92+
{
93+
// Passes
94+
Assert.Throws<InvalidOperationException>(() =>
95+
{
96+
Mapper.Map<Destination>(Source.Unspecified);
97+
});
98+
}
99+
100+
[Fact]
101+
public void TestReverseBarMapping()
102+
{
103+
// Passes
104+
var res = Mapper.Map<Source>(Destination.BAR_ALT_NAME);
105+
res.ShouldBe(Source.Bar);
106+
}
107+
108+
[Fact]
109+
public void TestReverseBazMapping()
110+
{
111+
// Failure: Expected: Baz But was: Bar => Fixed
112+
var res = Mapper.Map<Source>(Destination.BAZ_ALT_NAME);
113+
res.ShouldBe(Source.Baz);
114+
}
115+
}
116+
}
117+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
using System;
2+
using AutoMapper.Extensions.EnumMapping.Tests.Internal;
3+
using Shouldly;
4+
using Xunit;
5+
6+
namespace AutoMapper.Extensions.EnumMapping.Tests
7+
{
8+
public class ReverseEnumValueMappingByCustom
9+
{
10+
public class Valid : AutoMapperSpecBase
11+
{
12+
Destination _result;
13+
public enum Source { Default, Foo, Bar }
14+
public enum Destination { Default, Bar, Foo }
15+
16+
protected override MapperConfiguration Configuration { get; } = new MapperConfiguration(cfg =>
17+
{
18+
cfg.EnableEnumMappingValidation();
19+
cfg.CreateMap<Source, Destination>()
20+
.ConvertUsingEnumMapping(opt => opt
21+
.MapByCustom()
22+
.MapValue(Source.Default, Destination.Default)
23+
.MapValue(Source.Foo, Destination.Foo)
24+
.MapValue(Source.Bar, Destination.Bar)
25+
)
26+
.ReverseMap();
27+
});
28+
29+
protected override void Because_of()
30+
{
31+
_result = Mapper.Map<Source, Destination>(Source.Bar);
32+
}
33+
34+
[Fact]
35+
public void Should_map_enum_by_custom()
36+
{
37+
_result.ShouldBe(Destination.Bar);
38+
((int)_result).ShouldBe((int)Source.Foo);
39+
40+
}
41+
}
42+
43+
public class ValidCustomMapping : AutoMapperSpecBase
44+
{
45+
Destination _result;
46+
public enum Source { Default, Bar }
47+
public enum Destination { Default, Bar }
48+
49+
protected override MapperConfiguration Configuration { get; } = new MapperConfiguration(cfg =>
50+
{
51+
cfg.EnableEnumMappingValidation();
52+
cfg.CreateMap<Source, Destination>()
53+
.ConvertUsingEnumMapping(opt => opt
54+
.MapByCustom()
55+
.MapValue(Source.Default, Destination.Default)
56+
.MapValue(Source.Bar, Destination.Bar)
57+
)
58+
.ReverseMap();
59+
});
60+
61+
protected override void Because_of()
62+
{
63+
_result = Mapper.Map<Source, Destination>(Source.Bar);
64+
}
65+
66+
[Fact]
67+
public void Should_map_using_custom_map()
68+
{
69+
_result.ShouldBe(Destination.Bar);
70+
}
71+
}
72+
73+
public class ValidationErrors : NonValidatingSpecBase
74+
{
75+
public enum Source { Default, Foo, Bar }
76+
public enum Destination { Default, Bar }
77+
78+
protected override MapperConfiguration Configuration => new MapperConfiguration(cfg =>
79+
{
80+
cfg.EnableEnumMappingValidation();
81+
cfg.CreateMap<Source, Destination>()
82+
.ConvertUsingEnumMapping(opt => opt
83+
.MapByCustom()
84+
.MapValue(Source.Default, Destination.Default)
85+
.MapValue(Source.Bar, Destination.Bar)
86+
)
87+
.ReverseMap();
88+
});
89+
90+
[Fact]
91+
public void Should_fail_validation() =>
92+
new Action(() => Configuration.AssertConfigurationIsValid()).ShouldThrowException<AutoMapperConfigurationException>(
93+
ex => ex.Message.ShouldBe(
94+
$@"Missing enum mapping from {typeof(Source).FullName} to {typeof(Destination).FullName} based on Custom{Environment.NewLine}The following source values are not mapped:{Environment.NewLine} - Foo{Environment.NewLine}"));
95+
}
96+
97+
public class CustomMappingWithValidationErrors : NonValidatingSpecBase
98+
{
99+
public enum Source { Default, Foo, Bar, Error }
100+
public enum Destination { Default, Bar }
101+
102+
protected override MapperConfiguration Configuration { get; } = new MapperConfiguration(cfg =>
103+
{
104+
cfg.EnableEnumMappingValidation();
105+
cfg.CreateMap<Source, Destination>()
106+
.ConvertUsingEnumMapping(opt => opt
107+
.MapByCustom()
108+
.MapValue(Source.Default, Destination.Default)
109+
.MapException(Source.Foo, () => new NotSupportedException($"Foo is not valid value"))
110+
.MapValue(Source.Bar, Destination.Bar))
111+
.ReverseMap();
112+
});
113+
114+
[Fact]
115+
public void Should_fail_validation() =>
116+
new Action(() => Configuration.AssertConfigurationIsValid()).ShouldThrowException<AutoMapperConfigurationException>(
117+
ex => ex.Message.ShouldBe(
118+
$@"Missing enum mapping from {typeof(Source).FullName} to {typeof(Destination).FullName} based on Custom{Environment.NewLine}The following source values are not mapped:{Environment.NewLine} - Error{Environment.NewLine}"));
119+
}
120+
121+
public class ValidCustomReverseMapping : AutoMapperSpecBase
122+
{
123+
Source _resultDefault;
124+
Source _resultFoo;
125+
Source _resultBar;
126+
public enum Source { Default, Bar }
127+
public enum Destination { Default, Foo, Bar }
128+
129+
protected override MapperConfiguration Configuration { get; } = new MapperConfiguration(cfg =>
130+
{
131+
cfg.EnableEnumMappingValidation();
132+
cfg.CreateMap<Source, Destination>()
133+
.ConvertUsingEnumMapping(opt => opt
134+
.MapByCustom()
135+
.MapValue(Source.Default, Destination.Default)
136+
.MapValue(Source.Bar, Destination.Bar))
137+
.ReverseMap(optr => optr.MapByCustom().MapValue(Destination.Foo, Source.Bar));
138+
});
139+
140+
protected override void Because_of()
141+
{
142+
_resultDefault = Mapper.Map<Source>(Destination.Default);
143+
_resultFoo = Mapper.Map<Source>(Destination.Foo);
144+
_resultBar = Mapper.Map<Source>(Destination.Bar);
145+
}
146+
147+
[Fact]
148+
public void Should_map_using_reverse_custom_map()
149+
{
150+
_resultDefault.ShouldBe(Source.Default);
151+
_resultFoo.ShouldBe(Source.Bar);
152+
_resultBar.ShouldBe(Source.Bar);
153+
}
154+
}
155+
}
156+
}

src/AutoMapper.Extensions.EnumMapping/IEnumConfigurationExpression.cs

+6
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ public interface IEnumConfigurationExpression<in TSource, in TDestination>
2424
/// <returns>Enum configuration options</returns>
2525
IEnumConfigurationExpression<TSource, TDestination> MapByValue();
2626

27+
/// <summary>
28+
/// (default) Map enum values by custom mapping (no default mapping used)
29+
/// </summary>
30+
/// <returns>Enum configuration options</returns>
31+
IEnumConfigurationExpression<TSource, TDestination> MapByCustom();
32+
2733
/// <summary>
2834
/// Map enum value from source to destination value
2935
/// </summary>

0 commit comments

Comments
 (0)