1 /*
2     Contains class/struct mapper.
3 */
4 module automapper.mapper;
5 
6 import automapper.meta;
7 import automapper.naming;
8 import automapper.type.converter;
9 import automapper.value.transformer;
10 import automapper.config;
11 import automapper : RuntimeAutoMapper;
12 
13 
14 /// A mapper that map a source type to a dest. type.
15 interface IMapper(TSource, TDest)
16 {
17     ///
18     TDest map(TSource value);
19 }
20 
21 
22 /**
23     Allow to create compile-time generated class and struct mapper.
24 */
25 package class ObjectMapper(MapperConfig) : IMapper!(MapperConfig.TSource, MapperConfig.TDest)
26     if (isObjectMapperConfig!MapperConfig)
27 {
28     alias TSource = MapperConfig.TSource;
29     alias TDest = MapperConfig.TDest;
30 
31 public:
32     this(RuntimeAutoMapper automapper)
33     {
34         _automapper = automapper;
35     }
36 
37     // trick to get config specialization
38     static if (is(MapperConfig : ObjectMapperConfig!(TSource, TDest, TSourceConv, TDestConv, Reverse, Mappings),
39         TSourceConv, TDestConv, bool Reverse, Mappings...))
40     {
41         static TDest map(AutoMapper)(TSource source, AutoMapper am)
42         {
43             import std.algorithm : canFind;
44 
45             // init return value
46             static if (isClass!TDest)
47                 TDest b = new TDest();
48             else
49                 TDest b;
50 
51             // auto complete mappping
52             alias AutoMapping = Mappings;
53 
54             // warn about un-mapped members in B
55             static foreach(member; ClassMembers!TDest) {
56                 static if (!listMappedObjectMember!(AutoMapping).canFind(member)) {
57                     static assert(false, "non mapped member in destination object '" ~ TDest.stringof ~
58                         "." ~ member ~ "'");
59                 }
60             }
61 
62             // instanciate class member
63             static foreach(member; ClassMembers!TDest) {
64                 static if (isClass!(MemberType!(TDest, member))) {
65                     __traits(getMember, b, member) = new MemberType!(TDest, member);
66                 }
67             }
68 
69             // generate mapping code
70             static foreach(Mapping; AutoMapping) {
71                 static if (isObjectMemberMappingConfig!Mapping) {
72                     static assert(hasNestedMember!(TDest, Mapping.DestMember), Mapping.DestMember ~ 
73                         " is not a member of " ~ TDest.stringof);
74 
75                     // ForMember - mapMember
76                     static if (isForMember!(Mapping, ForMemberConfigType.mapMember)) {
77                         static assert(hasNestedMember!(TSource, Mapping.Action), Mapping.Action ~ 
78                             " is not a member of " ~ TSource.stringof);
79 
80                         // same type
81                         static if (is(MemberType!(TDest, Mapping.DestMember) == MemberType!(TSource, Mapping.Action))) {
82                             mixin(GetMember!(b, Mapping.DestMember)) = 
83                                 am.transform(mixin(GetMember!(source, Mapping.Action))); // b.member = a. member;
84                         }
85                         // different type: map
86                         else {
87                             __traits(getMember, b, Mapping.DestMember) = am.map!(
88                                 MemberType!(TDest, Mapping.DestMember),
89                                 MemberType!(TSource, Mapping.Action))(__traits(getMember, source, Mapping.Action)); // b.member = context.map(a.member);
90                         }
91                     }
92                     // ForMember - mapDelegate
93                     else static if (isForMember!(Mapping, ForMemberConfigType.mapDelegate)) {
94                         // static assert return type
95                         static assert(is(ReturnType!(Mapping.Action) == MemberType!(TDest, Mapping.DestMember)),
96                             "the func in " ~ ForMember.stringof ~ " must return a '" ~
97                             MemberType!(TDest, Mapping.DestMember).stringof ~ "' like " ~ TDest.stringof ~
98                             "." ~ Mapping.DestMember);
99                         // static assert parameters
100                         static assert(Parameters!(Mapping.Action).length is 1 && 
101                             is(Parameters!(Mapping.Action)[0] == TSource),
102                             "the func in " ~ ForMember.stringof ~ " must take a value of type '" ~ 
103                             TSource.stringof ~"'");
104                         __traits(getMember, b, Mapping.DestMember) = Mapping.Action(source);
105                     }
106                 }
107             }
108 
109             return b;
110         }
111 
112         TDest map(TSource a)
113         {
114             import std.algorithm : canFind;
115 
116             // init return value
117             static if (isClass!TDest)
118                 TDest b = new TDest();
119             else
120                 TDest b;
121 
122             // auto complete mappping
123             alias AutoMapping = Mappings;
124 
125             // warn about un-mapped members in B
126             static foreach(member; ClassMembers!TDest) {
127                 static if (!listMappedObjectMember!(AutoMapping).canFind(member)) {
128                     static assert(false, "non mapped member in destination object '" ~ TDest.stringof ~
129                         "." ~ member ~ "'");
130                 }
131             }
132 
133             // instanciate class member
134             static foreach(member; ClassMembers!TDest) {
135                 static if (isClass!(MemberType!(TDest, member))) {
136                     __traits(getMember, b, member) = new MemberType!(TDest, member);
137                 }
138             }
139 
140             // generate mapping code
141             static foreach(Mapping; AutoMapping) {
142                 static if (isObjectMemberMappingConfig!Mapping) {
143                     static assert(hasNestedMember!(TDest, Mapping.DestMember), Mapping.DestMember ~ 
144                         " is not a member of " ~ TDest.stringof);
145 
146                     // ForMember - mapMember
147                     static if (isForMember!(Mapping, ForMemberConfigType.mapMember)) {
148                         static assert(hasNestedMember!(TSource, Mapping.Action), Mapping.Action ~ 
149                         " is not a member of " ~ TSource.stringof);
150 
151                         // same type
152                         static if (is(MemberType!(TDest, Mapping.DestMember) == MemberType!(TSource, Mapping.Action))) {
153                             mixin(GetMember!(b, Mapping.DestMember)) = 
154                                 _automapper.transform(mixin(GetMember!(a, Mapping.Action))); // b.member = a. member;
155                         }
156                         // different type: map
157                         else {
158                             __traits(getMember, b, Mapping.DestMember) = _automapper.map!(
159                                 MemberType!(TDest, Mapping.DestMember),
160                                 MemberType!(TSource, Mapping.Action))(__traits(getMember, a, Mapping.Action)); // b.member = context.map(a.member);
161                         }
162                     }
163                     // ForMember - mapDelegate
164                     else static if (isForMember!(Mapping, ForMemberConfigType.mapDelegate)) {
165                         // static assert return type
166                         static assert(is(ReturnType!(Mapping.Action) == MemberType!(TDest, Mapping.DestMember)),
167                             "the func in " ~ ForMember.stringof ~ " must return a '" ~
168                             MemberType!(TDest, Mapping.DestMember).stringof ~ "' like " ~ TDest.stringof ~
169                             "." ~ Mapping.DestMember);
170                         // static assert parameters
171                         static assert(Parameters!(Mapping.Action).length is 1 && 
172                             is(Parameters!(Mapping.Action)[0] == TSource),
173                             "the func in " ~ ForMember.stringof ~ " must take a value of type '" ~ 
174                             TSource.stringof ~ "'");
175                         __traits(getMember, b, Mapping.DestMember) = Mapping.Action(a);
176                     }
177                 }
178             }
179 
180             return b;
181         }
182     }
183 
184 private:
185     RuntimeAutoMapper _automapper;
186 }
187 
188 package template isObjectMapper(T)
189 {
190     enum bool isObjectMapper = (is(T: ObjectMapper!(A, B), A, B) ||
191         is(T: ObjectMapper!(A, B, M), M));
192 }
193 
194 /**
195     It take a list of Mapper, and return a new list of reversed mapper if needed.
196 */
197 package template generateReversedMapperConfig(MapperConfigs...) if (allSatisfy!(isObjectMapperConfig, MapperConfigs))
198 {
199     import automapper.api;
200 
201     private template generateReversedMapperConfigImpl(size_t idx) {
202         static if (idx < MapperConfigs.length) {
203             alias Config = MapperConfigs[idx];
204 
205             private template reverseMapping(size_t midx) {
206                 static if (midx < Config.Mappings.length) {
207                     alias MP = Config.Mappings[midx];
208 
209                     static if (isForMember!(MP, ForMemberConfigType.mapMember)) {
210                         alias reverseMapping = AliasSeq!(ForMemberConfig!(MP.Action, MP.DestMember), 
211                             reverseMapping!(midx + 1));
212                     }
213                     else static if (isForMember!(MP, ForMemberConfigType.mapDelegate)) {
214                         static assert(false, "Cannot reverse mapping '" ~ Config.TSource.stringof ~ " -> " ~ 
215                             Config.TDest.stringof ~ "' because it use a custom user delegate: " ~ MP.stringof);
216                     }
217                     else
218                         alias reverseMapping = reverseMapping!(midx + 1); // continue
219                 }
220                 else
221                     alias reverseMapping = AliasSeq!();
222             }
223 
224             static if (Config.Reverse) // reverse it if needed
225                 alias generateReversedMapperConfigImpl = AliasSeq!(CreateMap!(Config.TDest, Config.TSource, 
226                     reverseMapping!0),
227                     generateReversedMapperConfigImpl!(idx + 1));
228             else
229                 alias generateReversedMapperConfigImpl = generateReversedMapperConfigImpl!(idx + 1); // continue
230         }
231         else
232             alias generateReversedMapperConfigImpl = AliasSeq!();
233     }
234 
235     alias generateReversedMapperConfig = generateReversedMapperConfigImpl!0;
236 }
237 
238 /**
239     List mapped object member.
240     Params:
241         Mappings = a list of ObjectMemberMapping
242     Returns:
243         a string[] of mapped member identifer
244 */
245 package template listMappedObjectMember(Mappings...) if (allSatisfy!(isObjectMemberMappingConfig, Mappings))
246 {
247     import std..string : split;
248 
249     private template listMappedObjectMemberImpl(size_t idx) {
250         static if (idx < Mappings.length)
251             static if (isObjectMemberMappingConfig!(Mappings[idx]))
252                 enum string[] listMappedObjectMemberImpl = Mappings[idx].DestMember ~ 
253                     Mappings[idx].DestMember.split(".") ~ listMappedObjectMemberImpl!(idx + 1);
254             else
255                 enum string[] listMappedObjectMemberImpl = listMappedObjectMemberImpl!(idx + 1); // skip
256         else
257             enum string[] listMappedObjectMemberImpl = [];
258     }
259 
260     enum string[] listMappedObjectMember = listMappedObjectMemberImpl!0;
261 }
262 
263 /**
264     Try to automatically map unmapper member.
265 
266     It:
267         * map member with the same name
268         * map flattened member to destination object
269           e.g: A.foo.bar is mapped to B.fooBar
270 
271     Params:
272         TSource = The type to map from
273         TDest = The type to map to
274         Mappings = a list of ObjectMemberMapping
275     Returns:
276         A list of completed ObjectMemberMapping
277 */
278 package template tryAutoMapUnmappedMembers(TSource, TDest, SourceConv, DestConv, Mappings...) if
279     (isNamingConvention!SourceConv && isNamingConvention!DestConv &&
280     allSatisfy!(isObjectMemberMappingConfig, Mappings))
281 {
282     import std.algorithm : canFind;
283     import std..string : join;
284 
285     //static if (is(TConfig : ObjectMapperConfig!(TSource, TDest, TSourceConv, TDestConv, Reverse, Mappings),
286 
287     enum MappedMembers = listMappedObjectMember!(Mappings);
288     enum SourceConvention = SourceConv();
289     enum DestConvention = DestConv();
290 
291     private template tryAutoMapUnmappedMembersImpl(size_t idx) {
292         static if (idx < FlattenedMembers!TSource.length) {
293             enum M = FlattenedMembers!TSource[idx];
294 
295             // un-mapped by user
296             static if (!MappedMembers.canFind(M)) {
297                 // TDest has this member
298                 // i.e: TDest.foo = TSource.foo
299                 static if (hasMember!(TDest, M)) {
300                     alias tryAutoMapUnmappedMembersImpl = AliasSeq!(ForMemberConfig!(M, M),
301                         tryAutoMapUnmappedMembersImpl!(idx+1));
302                 }
303                 // flatenning
304                 // TDest has a TSource member converted with DestConvention
305                 // i.e: TDest.fooBar = TSource.foo.bar
306                 else static if (hasMember!(TDest, DestConvention.convert(M))) {
307                     alias tryAutoMapUnmappedMembersImpl = AliasSeq!(ForMemberConfig!(SourceConvention.convert(M), M),
308                         tryAutoMapUnmappedMembersImpl!(idx+1));
309                 }
310                 // TDest with DestConvention has a TSource member converted using SourceConvention
311                 // i.e: TDest.foo_bar = TSource.fooBar
312                 else static if (hasMember!(TDest, DestConvention.convert(SourceConvention.convertBack(M)))) {
313                     alias tryAutoMapUnmappedMembersImpl = AliasSeq!(
314                         ForMemberConfig!(DestConvention.convert(SourceConvention.convertBack(M)), M),
315                         tryAutoMapUnmappedMembersImpl!(idx+1));
316                 }
317                 else
318                     alias tryAutoMapUnmappedMembersImpl = tryAutoMapUnmappedMembersImpl!(idx+1);
319             }
320             else
321                 alias tryAutoMapUnmappedMembersImpl = tryAutoMapUnmappedMembersImpl!(idx+1);
322         }
323         else
324             alias tryAutoMapUnmappedMembersImpl = AliasSeq!();
325     }
326 
327     alias tryAutoMapUnmappedMembers = AliasSeq!(tryAutoMapUnmappedMembersImpl!0, Mappings);
328 }