-
-
Notifications
You must be signed in to change notification settings - Fork 141
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
Map constant value to properties and constructor parameters #631
Comments
To match other attributes I'd name it It should apply to target properties and constructor parameters but it should not affect constructor resolution. |
Unfortunately
An alternative or fallback |
In a first version only primitive values would be supported. Later other attribute constructor overloads could be added, which allow passing an enum for „special non-constant values“ (creation of a new instance by calling a parameterless constructor, |
How is |
In a first version of this feature, even assigning value types default values and instantiating new values via a parameterless constructor could be achieved by passing |
I don't know how source generators work, but can't you just take the If this is not possible, then I assume Maybe also consider making the new attribute generic? (Generic attributes are possible since c# 11) |
I don't think we need to make the attribute generic. We can just assume, Mapperly usually works on the semantic model and not on the syntax, that's why it is not easily possible to tell whether |
It would also be advantageous to have the ability to map values using a function result instead of being constrained to using only constant values. Something like the below: public record Foo(string Id, string Name);
public record Bar(string Name);
[Mapper]
public class MyMapper
{
[MapValue(nameof(Foo.Id), nameof(GenerateId))]
public partial Foo ToFoo(Bar bar);
private string GenerateId()
{
// Id generation code...
}
} |
I'm not so sure about that one. It could lead to a "logic in mapper" approach which is an antipattern. |
What distinguishes the above "logic in mapper" approach from the already existing functionalities of Before/After Map and User Implemented Map? |
Good point. All of that can also be misused. |
User implemented map is to support type conversions not supported by Mapperly. |
Is it accurate to say that, despite initially planning to only support constant primitives, we still need to determine the overall structure of the API incorporating all the features discussed above? Maybe something like the below? public record Foo(string A, string B, Baz C, string D);
public record Bar(string D);
[Mapper]
public class MyMapper
{
[MapValue(nameof(Foo.A), From = "constant string")]
[MapValue(nameof(Foo.B), FromMethod = nameof(GenerateId))]
[MapValue(nameof(Foo.C), FromFactory = nameof(CreateBaz))]
public partial Foo ToFoo(Bar bar);
private string GenerateId()
{
// Id generation code...
}
[ObjectFactory]
private Baz CreateBaz()
{
// Instantiate and return Baz object...
}
} This resolves the concern of potential conflicts among different string overloads, but it doesn't guarantee compile-time safety. An error will need to be raised if precisely one of the three properties referenced above is not provided. |
The refactored member matching works as follows: * A member matching state and context is created * For each target member a matching source is looked up Now this happens unified for all types (constructor parameter, init property, ...) * A mapping is created with a MemberMappingInfo containing the information which members are mapped. * The mapping is added to the container. Based on the MemberMappingInfo the members are marked as mapped. * refactors and improves the readability of the member matching process * Introduces a new IgnoredMembersBuilder to build and validate ignored members * Introduces a new MemberMappingDiagnosticReporter to report all diagnostics after the member mapping build phase * Introduces a new NestedMappingsContext to handle nested mappings (MapPropertyFromSourceAttribute) * Introduces a new MemberMappingBuilder to build member (assignment) mappings for all types (objects, existing objects and tuples) * Introduces a new MembersMappingState holding the state during the member matching process (which members are not yet mapped, which are ignored, ...) * replace RMG017 and RMG027 with a new RMG074 when a target member path is used where it is not possible * Introduces a new MemberMappingInfo representing the mapped source and target members. This simplifies marking members as mapped * Introduces a new ConstructorParameterMember to simplify constructor parameter handling (can be treated as IMappableMember now) * Introduces a new ISourceValue interface which represents the right side of a member assignment mapping. For now only mapped source members and null mapped source members are supported. With #631 constant values and method provided values will also be ISourceValues BREAKING CHANGE: Replaces RMG017 and RMG027 with RMG074
Is your feature request related to a problem? Please describe.
If I have a target type that has a required property (or constructor parameter) and the source type does not have a matching property then I'm currently not able to use Mapperly to create the target object.
The only way to map such types right now is to use an object factory. But that results in ugly code because you need to initialize all required properties (or constructor parameters) in the object factory - even those that could be mapped from the source object. So this object factory might have many
Xyz = default
or (even worse)Xyz = default!
assignments (ordefault
/default!
constructor parameter values).Describe the solution you'd like
A new attribute like
[MapperSetDefaultValue(nameof(SomeEntity.Id))]
that would instruct the source generator to assigndefault
to the property (or constructor parameter) given in that attribute.Describe alternatives you've considered
A simpler solution that doesn't need a new attribute might be possible: The existing
MapperIgnoreTargetAttribute
could cause the source generator to assigndefault
if it is a required property (or constructor parameter).A more complex but also more customizable way to do this would be a property like
[MapperSetValue(nameof(SomeEntity.Id), 0)]
, so one where even non-default values could be supplied.Additional context
I'm not sure how nullable warnings are handled in generated code, but if the target property is not nullable then the source generator might have to assign
default!
. I'm not sure if this is a good idea. Maybe only allow this with an additional opt-in?If one of the suggested new attributes is implemented: There could be a situation where a user applies the new attribute although a matching source property is available. Maybe it would be a good idea to show an analyzer warning (or even an error) in that case, unless the
MapperIgnoreSourceAttribute
is used.Related discussion: #335
The text was updated successfully, but these errors were encountered: