Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ For method `CreateMap` this library provide a `ConvertUsingEnumMapping` method.

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

Default the enum values are mapped by value, but it is possible to map by name calling `MapByName()` or `MapByValue()`.
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.

```csharp
using AutoMapper.Extensions.EnumMapping;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
using System;
using System.Reflection;
using AutoMapper.Extensions.EnumMapping.Tests.Internal;
using Shouldly;
using Xunit;

namespace AutoMapper.Extensions.EnumMapping.Tests
{
/// <summary>
/// Based on issue #30
/// </summary>
public class ReverseCustomEnumMappingByCustom
{
public class Valid : AutoMapperSpecBase
{
Destination _result;

// Assume, as is the case for my use case, that both the source
// and destination enumerations are created via code generation
// from sources outside our control, and thus I cannot make updates
// or changes to them to work around the issue.

// This is idiomatic of how a Protobuf enum is generated, with the Unspecified
// value acting as a stand-in for when the field is not set by the client or server
public enum Source
{
Unspecified = 0,
Bar = 1,
Baz = 2,
}

// In our case, this is generated from an OpenAPI spec via Kiota
public enum Destination
{
BAR_ALT_NAME, // we can't map by name because the names don't match, even with case insensitivity turned on
BAZ_ALT_NAME,
}

public class TestEnumProfile : Profile
{
public TestEnumProfile()
{
CreateMap<Source, Destination>()
.ConvertUsingEnumMapping(
opts =>
{
opts
.MapByCustom()
.MapValue(Source.Bar, Destination.BAR_ALT_NAME)
.MapValue(Source.Baz, Destination.BAZ_ALT_NAME)
.MapException(Source.Unspecified, () => new InvalidOperationException($"Unspecified values are not supported"));
})
.ReverseMap();
}
}

protected override MapperConfiguration Configuration { get; } = new MapperConfiguration(cfg =>
{
cfg.EnableEnumMappingValidation();
cfg.AddMaps(typeof(ReverseCustomEnumMappingByCustom).GetTypeInfo().Assembly);
});

protected override void Because_of()
{
_result = Mapper.Map<Source, Destination>(Source.Bar);
}

[Fact]
public void Should_map_enum_by_value()
{
_result.ShouldBe(Destination.BAR_ALT_NAME);
}

[Fact]
public void TestBarMapping()
{
// Passes
var res = Mapper.Map<Destination>(Source.Bar);
res.ShouldBe(Destination.BAR_ALT_NAME);
}

[Fact]
public void TestBazMapping()
{
// Passes
var res = Mapper.Map<Destination>(Source.Baz);
res.ShouldBe(Destination.BAZ_ALT_NAME);
}

[Fact]
public void TestUnspecifiedMapping()
{
// Passes
Assert.Throws<InvalidOperationException>(() =>
{
Mapper.Map<Destination>(Source.Unspecified);
});
}

[Fact]
public void TestReverseBarMapping()
{
// Passes
var res = Mapper.Map<Source>(Destination.BAR_ALT_NAME);
res.ShouldBe(Source.Bar);
}

[Fact]
public void TestReverseBazMapping()
{
// Failure: Expected: Baz But was: Bar => Fixed
var res = Mapper.Map<Source>(Destination.BAZ_ALT_NAME);
res.ShouldBe(Source.Baz);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
using System;
using AutoMapper.Extensions.EnumMapping.Tests.Internal;
using Shouldly;
using Xunit;

namespace AutoMapper.Extensions.EnumMapping.Tests
{
public class ReverseEnumValueMappingByCustom
{
public class Valid : AutoMapperSpecBase
{
Destination _result;
public enum Source { Default, Foo, Bar }
public enum Destination { Default, Bar, Foo }

protected override MapperConfiguration Configuration { get; } = new MapperConfiguration(cfg =>
{
cfg.EnableEnumMappingValidation();
cfg.CreateMap<Source, Destination>()
.ConvertUsingEnumMapping(opt => opt
.MapByCustom()
.MapValue(Source.Default, Destination.Default)
.MapValue(Source.Foo, Destination.Foo)
.MapValue(Source.Bar, Destination.Bar)
)
.ReverseMap();
});

protected override void Because_of()
{
_result = Mapper.Map<Source, Destination>(Source.Bar);
}

[Fact]
public void Should_map_enum_by_custom()
{
_result.ShouldBe(Destination.Bar);
((int)_result).ShouldBe((int)Source.Foo);

}
}

public class ValidCustomMapping : AutoMapperSpecBase
{
Destination _result;
public enum Source { Default, Bar }
public enum Destination { Default, Bar }

protected override MapperConfiguration Configuration { get; } = new MapperConfiguration(cfg =>
{
cfg.EnableEnumMappingValidation();
cfg.CreateMap<Source, Destination>()
.ConvertUsingEnumMapping(opt => opt
.MapByCustom()
.MapValue(Source.Default, Destination.Default)
.MapValue(Source.Bar, Destination.Bar)
)
.ReverseMap();
});

protected override void Because_of()
{
_result = Mapper.Map<Source, Destination>(Source.Bar);
}

[Fact]
public void Should_map_using_custom_map()
{
_result.ShouldBe(Destination.Bar);
}
}

public class ValidationErrors : NonValidatingSpecBase
{
public enum Source { Default, Foo, Bar }
public enum Destination { Default, Bar }

protected override MapperConfiguration Configuration => new MapperConfiguration(cfg =>
{
cfg.EnableEnumMappingValidation();
cfg.CreateMap<Source, Destination>()
.ConvertUsingEnumMapping(opt => opt
.MapByCustom()
.MapValue(Source.Default, Destination.Default)
.MapValue(Source.Bar, Destination.Bar)
)
.ReverseMap();
});

[Fact]
public void Should_fail_validation() =>
new Action(() => Configuration.AssertConfigurationIsValid()).ShouldThrowException<AutoMapperConfigurationException>(
ex => ex.Message.ShouldBe(
$@"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}"));
}

public class CustomMappingWithValidationErrors : NonValidatingSpecBase
{
public enum Source { Default, Foo, Bar, Error }
public enum Destination { Default, Bar }

protected override MapperConfiguration Configuration { get; } = new MapperConfiguration(cfg =>
{
cfg.EnableEnumMappingValidation();
cfg.CreateMap<Source, Destination>()
.ConvertUsingEnumMapping(opt => opt
.MapByCustom()
.MapValue(Source.Default, Destination.Default)
.MapException(Source.Foo, () => new NotSupportedException($"Foo is not valid value"))
.MapValue(Source.Bar, Destination.Bar))
.ReverseMap();
});

[Fact]
public void Should_fail_validation() =>
new Action(() => Configuration.AssertConfigurationIsValid()).ShouldThrowException<AutoMapperConfigurationException>(
ex => ex.Message.ShouldBe(
$@"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}"));
}

public class ValidCustomReverseMapping : AutoMapperSpecBase
{
Source _resultDefault;
Source _resultFoo;
Source _resultBar;
public enum Source { Default, Bar }
public enum Destination { Default, Foo, Bar }

protected override MapperConfiguration Configuration { get; } = new MapperConfiguration(cfg =>
{
cfg.EnableEnumMappingValidation();
cfg.CreateMap<Source, Destination>()
.ConvertUsingEnumMapping(opt => opt
.MapByCustom()
.MapValue(Source.Default, Destination.Default)
.MapValue(Source.Bar, Destination.Bar))
.ReverseMap(optr => optr.MapByCustom().MapValue(Destination.Foo, Source.Bar));
});

protected override void Because_of()
{
_resultDefault = Mapper.Map<Source>(Destination.Default);
_resultFoo = Mapper.Map<Source>(Destination.Foo);
_resultBar = Mapper.Map<Source>(Destination.Bar);
}

[Fact]
public void Should_map_using_reverse_custom_map()
{
_resultDefault.ShouldBe(Source.Default);
_resultFoo.ShouldBe(Source.Bar);
_resultBar.ShouldBe(Source.Bar);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ public interface IEnumConfigurationExpression<in TSource, in TDestination>
/// <returns>Enum configuration options</returns>
IEnumConfigurationExpression<TSource, TDestination> MapByValue();

/// <summary>
/// (default) Map enum values by custom mapping (no default mapping used)
/// </summary>
/// <returns>Enum configuration options</returns>
IEnumConfigurationExpression<TSource, TDestination> MapByCustom();

/// <summary>
/// Map enum value from source to destination value
/// </summary>
Expand Down
Loading