-
-
Notifications
You must be signed in to change notification settings - Fork 149
Commit
- Loading branch information
There are no files selected for viewing
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
--- | ||
sidebar_position: 5 | ||
description: Manually implement mappings | ||
--- | ||
|
||
import Tabs from '@theme/Tabs'; | ||
|
||
# User implemented mapping methods | ||
|
||
If Mapperly cannot generate a mapping, one can implement it manually simply by providing a method body in the mapper declaration: | ||
|
||
```csharp | ||
[Mapper] | ||
public partial class CarMapper | ||
{ | ||
public partial CarDto CarToCarDto(Car car); | ||
|
||
private int TimeSpanToHours(TimeSpan t) => t.Hours; | ||
} | ||
``` | ||
|
||
Whenever Mapperly needs a mapping from `TimeSpan` to `int` inside the `CarMapper` implementation, it will use the provided implementation. | ||
|
||
## Use external mappings | ||
|
||
Mapperly can also consider mappings implemented in other classes. | ||
In order for Mapperly to find the mappings, they must be made known with `UseMapper` / `UseStaticMapper`. | ||
|
||
<!-- do not indent this, it won't work, https://stackoverflow.com/a/67579641/3302887 --> | ||
|
||
<Tabs> | ||
<TabItem value="static" label="Static"> | ||
|
||
For static mappings, `UseStaticMapper` can be used: | ||
```csharp | ||
[Mapper] | ||
// highlight-start | ||
[UseStaticMapper<BananaMapper>] // for c# language level ≥ 11 | ||
[UseStaticMapper(typeof(BananaMapper))] // for c# language level < 11 | ||
// highlight-end | ||
public static partial class BoxMapper | ||
{ | ||
public static partial BananaBox MapBananaBox(BananaBoxDto dto); | ||
} | ||
|
||
public static class BananaMapper | ||
{ | ||
public static Banana MapBanana(BananaDto dto) | ||
=> new Banana(dto.Weigth); | ||
} | ||
``` | ||
|
||
</TabItem> | ||
<TabItem value="instance" label="Instance"> | ||
|
||
To use the mappings of an object instance `UseMapper` can be used: | ||
|
||
```csharp | ||
[Mapper] | ||
public static partial class BoxMapper | ||
{ | ||
// highlight-start | ||
[UseMapper] | ||
private readonly BananaMapper _bananaMapper = new(); | ||
// highlight-end | ||
public static partial BananaBox MapBananaBox(BananaBoxDto dto); | ||
} | ||
|
||
public static class BananaMapper | ||
{ | ||
public static Banana MapBanana(BananaDto dto) | ||
=> new Banana(dto.Weigth); | ||
} | ||
``` | ||
|
||
:::info | ||
The initialization of fields and properties annotated with `UseMapper` needs to be done by the user. | ||
::: | ||
|
||
</TabItem> | ||
</Tabs> | ||
|
||
Whenever Mapperly needs a mapping from `BananaBox` to `BananaBoxDto` inside the `BoxMapper` implementation, | ||
it will use the provided implementation by the `BananaMapper`. | ||
|
||
Used mappers themselves can be Mapperly backed classes. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
namespace Riok.Mapperly.Abstractions; | ||
|
||
/// <summary> | ||
/// Considers all accessible mapping methods provided by the type of this member. | ||
/// Includes static and instance methods. | ||
/// </summary> | ||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] | ||
public sealed class UseMapperAttribute : Attribute { } |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
namespace Riok.Mapperly.Abstractions; | ||
|
||
/// <summary> | ||
/// Considers all static mapping methods provided by the type. | ||
/// </summary> | ||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] | ||
public sealed class UseStaticMapperAttribute : Attribute | ||
{ | ||
/// <summary> | ||
/// Considers all static mapping methods provided by the <paramref name="mapperType"/>. | ||
/// </summary> | ||
/// <param name="mapperType">The type of which mapping methods will be included.</param> | ||
public UseStaticMapperAttribute(Type mapperType) { } | ||
} | ||
|
||
/// <summary> | ||
/// Considers all static mapping methods provided by the generic type. | ||
/// </summary> | ||
/// <typeparam name="T">The type of which mapping methods will be included.</typeparam> | ||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] | ||
public sealed class UseStaticMapperAttribute<T> : Attribute { } |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
using Microsoft.CodeAnalysis; | ||
|
||
namespace Riok.Mapperly.Configuration; | ||
|
||
public record UseStaticMapperConfiguration(ITypeSymbol MapperType); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
using Microsoft.CodeAnalysis; | ||
using Riok.Mapperly.Abstractions; | ||
using Riok.Mapperly.Configuration; | ||
using Riok.Mapperly.Descriptors.Mappings.UserMappings; | ||
using Riok.Mapperly.Diagnostics; | ||
using Riok.Mapperly.Helpers; | ||
|
||
namespace Riok.Mapperly.Descriptors.ExternalMappings; | ||
|
||
internal static class ExternalMappingsExtractor | ||
{ | ||
public static IEnumerable<IUserMapping> ExtractExternalMappings(SimpleMappingBuilderContext ctx, INamedTypeSymbol mapperSymbol) | ||
{ | ||
// TODO extract attribute data accessor | ||
Check warning on line 14 in src/Riok.Mapperly/Descriptors/ExternalMappings/ExternalMappingsExtractor.cs GitHub Actions / build
Check warning on line 14 in src/Riok.Mapperly/Descriptors/ExternalMappings/ExternalMappingsExtractor.cs GitHub Actions / build
Check warning on line 14 in src/Riok.Mapperly/Descriptors/ExternalMappings/ExternalMappingsExtractor.cs GitHub Actions / MappingBenchmarks
Check warning on line 14 in src/Riok.Mapperly/Descriptors/ExternalMappings/ExternalMappingsExtractor.cs GitHub Actions / SourceGeneratorBenchmarks
Check warning on line 14 in src/Riok.Mapperly/Descriptors/ExternalMappings/ExternalMappingsExtractor.cs GitHub Actions / sample
Check warning on line 14 in src/Riok.Mapperly/Descriptors/ExternalMappings/ExternalMappingsExtractor.cs GitHub Actions / package
Check warning on line 14 in src/Riok.Mapperly/Descriptors/ExternalMappings/ExternalMappingsExtractor.cs GitHub Actions / package
Check warning on line 14 in src/Riok.Mapperly/Descriptors/ExternalMappings/ExternalMappingsExtractor.cs GitHub Actions / package
Check warning on line 14 in src/Riok.Mapperly/Descriptors/ExternalMappings/ExternalMappingsExtractor.cs GitHub Actions / Analyze (csharp)
|
||
var accessor = new AttributeDataAccessor(ctx.SymbolAccessor); | ||
|
||
var staticExternalMappers = accessor | ||
.Access<UseStaticMapperAttribute, UseStaticMapperConfiguration>(mapperSymbol) | ||
.Concat(accessor.Access<UseStaticMapperAttribute<object>, UseStaticMapperConfiguration>(mapperSymbol)) | ||
.SelectMany( | ||
x => | ||
UserMethodMappingExtractor.ExtractUserImplementedMappings( | ||
ctx, | ||
x.MapperType, | ||
x.MapperType.FullyQualifiedIdentifierName(), | ||
true | ||
) | ||
); | ||
|
||
var externalInstanceMappers = ctx.SymbolAccessor | ||
.GetAllMembers(mapperSymbol) | ||
.SelectMany(x => ValidateAndExtractExternalInstanceMappings(ctx, x)); | ||
|
||
return staticExternalMappers.Concat(externalInstanceMappers); | ||
} | ||
|
||
private static IEnumerable<IUserMapping> ValidateAndExtractExternalInstanceMappings(SimpleMappingBuilderContext ctx, ISymbol symbol) | ||
{ | ||
var (name, type, nullableAnnotation) = symbol switch | ||
{ | ||
IFieldSymbol field => (field.Name, field.Type, field.NullableAnnotation), | ||
IPropertySymbol prop => (prop.Name, prop.Type, prop.NullableAnnotation), | ||
_ => (string.Empty, null, NullableAnnotation.None), | ||
}; | ||
|
||
if (type == null) | ||
return Enumerable.Empty<IUserMapping>(); | ||
|
||
if (nullableAnnotation != NullableAnnotation.Annotated) | ||
return UserMethodMappingExtractor.ExtractUserImplementedMappings(ctx, type, name, false); | ||
|
||
ctx.ReportDiagnostic(DiagnosticDescriptors.ExternalMapperMemberCannotBeNullable, symbol, symbol.ToDisplayString()); | ||
return Enumerable.Empty<IUserMapping>(); | ||
} | ||
} |