Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix issue #537 - Step 2 - Add support Extended Record Function + Attribute from support used current record definitions #646

Merged
merged 16 commits into from
Jan 2, 2025
Merged
17 changes: 17 additions & 0 deletions src/Mapster.Core/Attributes/AdaptWithAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;

namespace Mapster
{
[AttributeUsage(AttributeTargets.Class
| AttributeTargets.Struct
| AttributeTargets.Property
| AttributeTargets.Field, AllowMultiple = true)]
public class AdaptWithAttribute : Attribute
{
public AdaptDirectives AdaptDirective { get; set; }
public AdaptWithAttribute(AdaptDirectives directive)
{
AdaptDirective = directive;
}
}
}
8 changes: 8 additions & 0 deletions src/Mapster.Core/Enums/AdaptDirectives.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Mapster
{
public enum AdaptDirectives
{
None = 0,
DestinationAsRecord = 1
}
}
17 changes: 15 additions & 2 deletions src/Mapster.Core/Utils/RecordTypeIdentityHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ private static bool IsRecordСonstructor(Type type)
if (ctors.Count < 2)
return false;

var isRecordTypeCtor = type.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic)
var isRecordTypeCtor =
ctors
.Where(x => x.IsFamily == true || (type.IsSealed && x.IsPrivate == true)) // add target from Sealed record
.Any(x => x.GetParameters()
.Any(y => y.ParameterType == type));
.Any(y => y.ParameterType == type));

if (isRecordTypeCtor)
return true;
Expand All @@ -43,5 +44,17 @@ public static bool IsRecordType(Type type)

return false;
}

public static bool IsDirectiveTagret(Type type)
{
var arrt = type.GetCustomAttributes<AdaptWithAttribute>()?.FirstOrDefault()?.AdaptDirective;

if (arrt == null)
return false;
if (arrt == AdaptDirectives.DestinationAsRecord)
return true;

return false;
}
}
}
4 changes: 2 additions & 2 deletions src/Mapster.Tests/WhenIgnoringConditionally.cs
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,6 @@ public void IgnoreIf_Can_Be_Combined()
public void IgnoreIf_Apply_To_RecordType()
{
TypeAdapterConfig<SimplePoco, SimpleRecord>.NewConfig()
.EnableNonPublicMembers(true) // add or
.IgnoreIf((src, dest) => src.Name == "TestName", dest => dest.Name)
.Compile();

Expand Down Expand Up @@ -188,7 +187,8 @@ public class SimpleDto
public string Name { get; set; }
}

public class SimpleRecord // or Replace on record
[AdaptWith(AdaptDirectives.DestinationAsRecord)]
public class SimpleRecord
{
public int Id { get; }
public string Name { get; }
Expand Down
45 changes: 25 additions & 20 deletions src/Mapster.Tests/WhenMappingPrivateFieldsAndProperties.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public void Should_Map_Private_Field_To_New_Object_Correctly()
dto.Name.ShouldBe(customerName);
}


[TestMethod]
public void Should_Map_Private_Property_To_New_Object_Correctly()
{
Expand All @@ -78,21 +79,20 @@ public void Should_Map_Private_Property_To_New_Object_Correctly()
}

[TestMethod]
public void Should_Map_To_Private_Fields_Correctly()
public void Should_Map_To_Private_Fields_Correctly()
{
SetUpMappingNonPublicFields<CustomerDTO, CustomerWithPrivateField>();
var dto = new CustomerDTO
SetUpMappingNonPublicFields<CustomerDTOWithPrivateGet, CustomerWithPrivateField>();

var dto = new CustomerDTOWithPrivateGet
{
Id = 1,
Name = "Customer 1"
};

var customer = dto.Adapt<CustomerWithPrivateField>();
var customer = dto.Adapt<CustomerWithPrivateField>();

Assert.IsNotNull(customer);
Assert.IsTrue(customer.HasId(dto.Id));
customer.Name.ShouldBe(dto.Name);
customer.HasId().ShouldBe(1);
customer.Name.ShouldBe("Customer 1");
}

[TestMethod]
Expand All @@ -108,9 +108,8 @@ public void Should_Map_To_Private_Properties_Correctly()

var customer = dto.Adapt<CustomerWithPrivateProperty>();

Assert.IsNotNull(customer);
customer.Id.ShouldBe(dto.Id);
Assert.IsTrue(customer.HasName(dto.Name));
customer.Id.ShouldBe(1);
customer.HasName().ShouldBe("Customer 1");
}

[TestMethod]
Expand Down Expand Up @@ -167,39 +166,39 @@ private static void SetUpMappingNonPublicProperties<TSource, TDestination>()

public class CustomerWithPrivateField
{
private readonly int _id;
private int _id;
public string Name { get; private set; }

private CustomerWithPrivateField() { }
public CustomerWithPrivateField() { }

public CustomerWithPrivateField(int id, string name)
{
_id = id;
Name = name;
}

public bool HasId(int id)
public int HasId()
{
return _id == id;
return _id;
}
}

public class CustomerWithPrivateProperty
public class CustomerWithPrivateProperty
{
public int Id { get; private set; }
private string Name { get; set; }

private CustomerWithPrivateProperty() { }
public CustomerWithPrivateProperty() { }

public CustomerWithPrivateProperty(int id, string name)
public CustomerWithPrivateProperty(int id, string name)
{
Id = id;
Name = name;
}

public bool HasName(string name)
public string HasName()
{
return Name == name;
return Name;
}
}

Expand Down Expand Up @@ -228,6 +227,12 @@ public class CustomerDTO
public string Name { get; set; }
}

public class CustomerDTOWithPrivateGet
{
public int Id { private get; set; }
public string Name { private get; set; }
}

public class Pet
{
public string Name { get; set; }
Expand Down
140 changes: 101 additions & 39 deletions src/Mapster.Tests/WhenMappingRecordRegression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public void AdaptRecordStructToRecordStruct()
var _structResult = _sourceStruct.Adapt(_destinationStruct);

_structResult.X.ShouldBe(1000);
object.ReferenceEquals(_destinationStruct, _structResult).ShouldBeFalse();
_destinationStruct.X.Equals(_structResult.X).ShouldBeFalse();
}

[TestMethod]
Expand Down Expand Up @@ -194,26 +194,6 @@ public void UpdateNullable()

}

/// <summary>
/// https://github.com/MapsterMapper/Mapster/issues/524
/// </summary>
[TestMethod]
public void TSousreIsObjectUpdateUseDynamicCast()
{
var source = new TestClassPublicCtr { X = 123 };
var _result = SomemapWithDynamic(source);

_result.X.ShouldBe(123);
}

TestClassPublicCtr SomemapWithDynamic(object source)
{
var dest = new TestClassPublicCtr { X = 321 };
var dest1 = source.Adapt(dest,source.GetType(),dest.GetType());

return dest;
}

/// <summary>
/// https://github.com/MapsterMapper/Mapster/issues/569
/// </summary>
Expand Down Expand Up @@ -247,6 +227,56 @@ public void DetectFakeRecord()
_destination.X.ShouldBe(200);
object.ReferenceEquals(_destination, _result).ShouldBeTrue();
}

[TestMethod]
public void OnlyInlineRecordWorked()
{
var _sourcePoco = new InlinePoco501() { MyInt = 1 , MyString = "Hello" };
var _sourceOnlyInitRecord = new OnlyInitRecord501 { MyInt = 2, MyString = "Hello World" };

var _resultOnlyinitRecord = _sourcePoco.Adapt<OnlyInitRecord501>();
var _updateResult = _sourceOnlyInitRecord.Adapt(_resultOnlyinitRecord);

_resultOnlyinitRecord.MyInt.ShouldBe(1);
_resultOnlyinitRecord.MyString.ShouldBe("Hello");
_updateResult.MyInt.ShouldBe(2);
_updateResult.MyString.ShouldBe("Hello World");
}

[TestMethod]
public void MultyCtorRecordWorked()
{
var _sourcePoco = new InlinePoco501() { MyInt = 1, MyString = "Hello" };
var _sourceMultyCtorRecord = new MultiCtorRecord (2, "Hello World");

var _resultMultyCtorRecord = _sourcePoco.Adapt<MultiCtorRecord>();
var _updateResult = _sourceMultyCtorRecord.Adapt(_resultMultyCtorRecord);

_resultMultyCtorRecord.MyInt.ShouldBe(1);
_resultMultyCtorRecord.MyString.ShouldBe("Hello");
_updateResult.MyInt.ShouldBe(2);
_updateResult.MyString.ShouldBe("Hello World");
}

[TestMethod]
public void MultiCtorAndInlineRecordWorked()
{
var _sourcePoco = new MultiCtorAndInlinePoco() { MyInt = 1, MyString = "Hello", MyEmail = "[email protected]", InitData="Test"};
var _sourceMultiCtorAndInline = new MultiCtorAndInlineRecord(2, "Hello World") { InitData = "Worked", MyEmail = "[email protected]" };

var _resultMultiCtorAndInline = _sourcePoco.Adapt<MultiCtorAndInlineRecord>();
var _updateResult = _sourceMultiCtorAndInline.Adapt(_resultMultiCtorAndInline);

_resultMultiCtorAndInline.MyInt.ShouldBe(1);
_resultMultiCtorAndInline.MyString.ShouldBe("Hello");
_resultMultiCtorAndInline.MyEmail.ShouldBe("[email protected]");
_resultMultiCtorAndInline.InitData.ShouldBe("Test");
_updateResult.MyInt.ShouldBe(2);
_updateResult.MyString.ShouldBe("Hello World");
_updateResult.MyEmail.ShouldBe("[email protected]");
_updateResult.InitData.ShouldBe("Worked");
}


#region NowNotWorking

Expand All @@ -268,35 +298,67 @@ public void CollectionUpdate()
destination.Count.ShouldBe(_result.Count);
}

/// <summary>
/// https://github.com/MapsterMapper/Mapster/issues/524
/// Not work. Already has a special overload:
/// .Adapt(this object source, object destination, Type sourceType, Type destinationType)
/// </summary>
[Ignore]
[TestMethod]
public void TSousreIsObjectUpdate()
{
var source = new TestClassPublicCtr { X = 123 };
var _result = Somemap(source);
#endregion NowNotWorking

}


_result.X.ShouldBe(123);
#region TestClasses

class MultiCtorAndInlinePoco
{
public int MyInt { get; set; }
public string MyString { get; set; }
public string MyEmail { get; set; }
public string InitData { get; set; }
}

record MultiCtorAndInlineRecord
{
public MultiCtorAndInlineRecord(int myInt)
{
MyInt = myInt;
}

TestClassPublicCtr Somemap(object source)
public MultiCtorAndInlineRecord(int myInt, string myString) : this(myInt)
{
var dest = new TestClassPublicCtr { X = 321 };
var dest1 = source.Adapt(dest); // typeof(TSource) always return Type as Object. Need use dynamic or Cast to Runtime Type before Adapt
MyString = myString;
}


public int MyInt { get; private set; }
public string MyString { get; private set; }
public string MyEmail { get; set; }
public string InitData { get; init; }
}

return dest;
record MultiCtorRecord
{
public MultiCtorRecord(int myInt)
{
MyInt = myInt;
}

#endregion NowNotWorking
public MultiCtorRecord(int myInt, string myString) : this(myInt)
{
MyString = myString;
}

public int MyInt { get; private set; }
public string MyString { get; private set; }
}

class InlinePoco501
{
public int MyInt { get; set; }
public string MyString { get; set; }
}

#region TestClasses
record OnlyInitRecord501
{
public int MyInt { get; init; }
public string MyString { get; init; }
}

class PocoWithGuid
{
Expand Down
Loading
Loading