1 /**
2     Automatic compile-time generated object mapper.
3 
4     This module aims to provide a compile-time generated way to
5     create object mapper with no runtime overhead.
6 
7     Features:
8 
9     $(UL
10 	    $(LI Object and struct automatic mapping: See `AutoMapper`)
11 	    $(LI Type converter: See `automapper.type.converter`)
12 	    $(LI Value transformer: See `automapper.value.transformer`)
13 	)
14 
15     To get an AutoMapper instance you need to provide a `automapper.config.MapperConfiguration`.
16 
17 */
18 module automapper;
19 
20 import automapper.meta;
21 import automapper.value.transformer;
22 
23 public import automapper.api;
24 public import automapper.type.converter;
25 public import automapper.mapper;
26 public import automapper.naming;
27 
28 
29 /**
30     AutoMapper entry point.
31 
32     Used to create low runtime overhead mapper for object or struct.
33 
34     Mappings are compile-time checked.
35 */
36 class AutoMapper(MC) if (is(MC : MapperConfiguration!(C), C))
37 {
38     import std.format : format;
39 
40 private:
41     alias TypesConverters = MC.TypesConverters;
42     alias ValueTransformers = MC.ValueTransformers;
43 
44     template completeMapperConfiguration()
45     {
46         private template completeMapperConfigurationImpl(size_t idx) {
47             static if (idx < MC.ObjectMappersConfig.length) {
48                 alias CurrentConfig = MC.ObjectMappersConfig[idx];
49 
50                  // trick to get config specialization
51                 static if (is(CurrentConfig : ObjectMapperConfig!(TSource, TDest, TSourceConv, 
52                     TDestConv, Reverse, Mappings),
53                     TSource, TDest, TSourceConv, TDestConv, bool Reverse, Mappings...)) {
54 
55                     alias NewConfig = tryAutoMapUnmappedMembers!(TSource, TDest, TSourceConv, TDestConv, Mappings);
56                     alias completeMapperConfigurationImpl = AliasSeq!(
57                         ObjectMapperConfig!(TSource, TDest, TSourceConv, TDestConv, Reverse, NewConfig),
58                         completeMapperConfigurationImpl!(idx + 1));
59                 }
60             }
61             else
62                 alias completeMapperConfigurationImpl = AliasSeq!();
63         }
64 
65         alias completeMapperConfiguration = completeMapperConfigurationImpl!0;
66     }
67 
68     alias CompletedConfig = completeMapperConfiguration!();
69     alias FullMapperConfigs = AliasSeq!(CompletedConfig, generateReversedMapperConfig!(CompletedConfig));
70 
71     template buildMapper()
72     {
73         private template buildMapperImpl(size_t idx) {
74             static if (idx < FullMapperConfigs.length) {
75                 alias MPC = FullMapperConfigs[idx];
76 
77                 alias buildMapperImpl = AliasSeq!(
78                     ObjectMapper!MPC,
79                     buildMapperImpl!(idx + 1));
80             }
81             else
82                 alias buildMapperImpl = AliasSeq!();
83         }
84         alias buildMapper = buildMapperImpl!0;
85     }
86 
87 
88     alias FullMappers = buildMapper!();
89 
90 
91     // run-time
92     string runtimeUniqueMapperIdentifier(TypeInfo a, TypeInfo b)
93     {
94         import std..string : replace;
95 
96         return ("mapper_" ~ a.toString() ~ "_" ~ b.toString()).replace(".", "_");
97     }
98 
99     /// compile-time
100     template uniqueConverterIdentifier(T)
101     {
102         import std..string : replace;
103         static if (is(T : ITypeConverter!(A, B), A, B))
104             enum string uniqueConverterIdentifier = ("conv_" ~ fullyQualifiedName!A ~ "_" ~ fullyQualifiedName!B)
105                 .replace(".", "_");
106     }
107 
108     // declare private registered ITypeConverter
109     static foreach (Conv; TypesConverters) {
110         static if (is(Conv : ITypeConverter!(A, B), A, B)) {
111             mixin(q{private ITypeConverter!(%s, %s) %s; }.format(fullyQualifiedName!A, fullyQualifiedName!B,
112                 uniqueConverterIdentifier!Conv));
113         }
114     }
115 
116     /// compile-time
117     template uniqueTransformerIdentifier(A)
118     {
119         import std..string : replace;
120         enum string uniqueTransformerIdentifier = ("trans" ~ fullyQualifiedName!A).replace(".", "_");
121     }
122 
123     // declare private registered IValueTransformer
124     static foreach (Trans; ValueTransformers) {
125         static if (is(Trans : IValueTransformer!TValue, TValue)) {
126             mixin(q{private IValueTransformer!TValue %s; }.format(
127                 uniqueTransformerIdentifier!TValue));
128         }
129     }
130 
131 public:
132     ///
133     this()
134     {
135         // instanciate registered ITypeConverter
136         static foreach (Conv; TypesConverters)
137             mixin(q{%s = new Conv(); }.format(uniqueConverterIdentifier!Conv));
138 
139         // instanciate registered IValueTransformer
140         static foreach (Trans; ValueTransformers)
141             static if (is(Trans : IValueTransformer!TValue, TValue))
142                 mixin(q{%s = new Trans(); }.format(uniqueTransformerIdentifier!TValue));
143     }
144 
145     /// It create a RuntimeAutoMapper that is an AutoMapper more suitable for runtime operation.
146     /// It means that errors are thrown at runtime. 
147     RuntimeAutoMapper createRuntimeContext()
148     {
149         auto context = new RuntimeAutoMapper();
150 
151         // fill mapper
152         static foreach (Mapper; FullMappers)
153             context.runtimeMappers[typeid(IMapper!(Mapper.TSource, Mapper.TDest))] = new Mapper(context);
154 
155         // fill converters
156         static foreach (Conv; TypesConverters)
157             static if (is(Conv : ITypeConverter!(TSource, TDest), TSource, TDest))
158                 context.converters[typeid(ITypeConverter!(TSource, TDest))] = new Conv();
159 
160         // fill transformers
161         static foreach (Trans; ValueTransformers)
162             static if (is(Trans : IValueTransformer!TValue, TValue))
163                 context.transformers[typeid(IValueTransformer!TValue)] = new Trans();
164 
165         return context;
166     }   
167 
168     /**
169         Map an object to another.
170         Params:
171             source = The type to map
172         Retuns:
173             The mapped object
174     */
175     TDest map(TDest, TSource)(TSource source) if (isClassOrStruct!TSource && isClassOrStruct!TDest)
176     {
177         template isRightMapper(T) {
178             enum bool isRightMapper = (isInstanceOf!(ObjectMapper, T) && is(T.TSource : TSource) && 
179                 is(T.TDest : TDest));
180         }
181 
182         alias M = Filter!(isRightMapper, FullMappers);
183 
184         static if (M.length is 0)
185             static assert(false, "No mapper found for mapping from " ~ TSource.stringof ~ " to " ~ TDest.stringof);
186         else
187             return M[0].map(source, this);
188     }
189 
190     /// ditto
191     TDest map(TDest, TSource)(TSource source) if (isArray!TSource && isArray!TDest)
192     {
193         TDest ret = TDest.init;
194 
195         foreach(ForeachType!TSource elem; source) {
196             static if (is(ForeachType!TSource == ForeachType!TDest))
197                 ret ~= elem; // same array type, just copy
198             else
199                 ret ~= this.map!(ForeachType!TDest)(elem); // else map
200         }
201 
202         return ret;
203     }
204 
205     /// ditto
206     TDest map(TDest, TSource)(TSource source) if 
207         (!isArray!TSource && !isArray!TDest && (!isClassOrStruct!TSource || !isClassOrStruct!TDest))
208     {
209         template isRightConverter(T) {
210             enum bool isRightConverter = is(T : ITypeConverter!(TSource, TDest));
211         }
212 
213         alias M = Filter!(isRightConverter, TypesConverters);
214 
215         static if (M.length is 0)
216             static assert(false, "No type converter found for mapping from " ~ TSource.stringof ~ " to " ~ 
217                 TDest.stringof);
218         else
219             return __traits(getMember, this, uniqueConverterIdentifier!M).convert(source);
220     }
221 
222     ///
223     TValue transform(TValue)(TValue value)
224     {
225         template isRightTransformer(T) {
226             enum bool isRightTransformer = is(T : IValueTransformer!(TValue));
227         }
228 
229         alias Transformer = Filter!(isRightTransformer, ValueTransformers);
230 
231         static if (Transformer.length is 0) {
232             pragma(inline, true);
233             return value;
234         }
235         else
236             return __traits(getMember, this, uniqueTransformerIdentifier!TValue).transform(value);
237     }
238 }
239 
240 /**
241     This is a non templated slower version of AutoMapper.
242 */
243 class RuntimeAutoMapper
244 {
245     package Object[TypeInfo] runtimeMappers;
246     package Object[TypeInfo] transformers;
247     package Object[TypeInfo] converters;
248 
249 public:
250     /// Retrive a mapper instance.
251     IMapper!(TSource, TDest) getMapper(TDest, TSource)()
252     {
253         alias I = IMapper!(TSource, TDest);
254         auto info = typeid(I);
255 
256         Object* mapper = (info in runtimeMappers);
257 
258         if (mapper !is null) {
259             return (cast(I) *mapper);
260         }
261         else {
262             return null;
263         }
264     }
265 
266     /// Map a source object to a dest object.
267     TDest map(TDest, TSource)(TSource value) if (isClassOrStruct!TSource && isClassOrStruct!TDest)
268     {
269         // enum id = uniquePairIdentifier!(TSource, TDest);
270         alias I = IMapper!(TSource, TDest);
271         auto info = typeid(I);
272 
273         Object* mapper = (info in runtimeMappers);
274 
275         if (mapper !is null) {
276             return (cast(I) *mapper).map(value);
277         }
278         else {
279             throw new Exception("No mapper found for mapping from " ~ TSource.stringof ~ " to " ~ TDest.stringof);
280         }
281     }
282 
283     /// ditto
284     TDest map(TDest, TSource)(TSource source) if (isArray!TSource && isArray!TDest)
285     {
286         TDest ret = TDest.init;
287 
288         foreach(ForeachType!TSource elem; source) {
289             static if (is(ForeachType!TSource == ForeachType!TDest))
290                 ret ~= elem; // same array type, just copy
291             else
292                 ret ~= this.map!(ForeachType!TDest)(elem); // else map
293         }
294 
295         return ret;
296     }
297 
298     /// ditto
299     TDest map(TDest, TSource)(TSource source)
300         if (!isArray!TDest && (!isClassOrStruct!TSource || !isClassOrStruct!TDest))
301     {
302         alias I = ITypeConverter!(TSource, TDest);
303         auto info = typeid(I);
304 
305         Object* converter = (info in converters);
306 
307         if (converters !is null) {
308             return (cast(I) *converter).convert(source);
309         }
310         else {
311             throw new Exception("No converter found for converting from " ~ TSource.stringof ~ " to " ~ TDest.stringof);
312         }
313     }
314 
315     /// Transform a value using registered value transformers.
316     TValue transform(TValue)(TValue value)
317     {
318         // enum id = uniqueTypeIdentifier!TValue;
319         alias I = IValueTransformer!TValue;
320         auto info = typeid(I);
321 
322         Object* transformer = (info in transformers);
323 
324         if (transformer !is null) {
325             return (cast(I) *transformer).transform(value);
326         }
327         else {
328             return value;
329         }
330     }
331 }
332 
333 ///
334 unittest
335 {
336     import std.datetime : SysTime;
337     import automapper;
338 
339     static class A {
340         string foo = "foo";
341         long timestamp;
342     }
343 
344     static class B {
345         string bar;
346         SysTime timestamp;
347     }
348 
349     RuntimeAutoMapper am = MapperConfiguration!(
350         CreateMap!(long, SysTime)
351             .ConvertUsing!((long ts) => SysTime(ts)),
352         CreateMap!(A, B)
353             .ForMember!("bar", "foo"))
354                 .createMapper().createRuntimeContext();
355 
356     am.map!B(new A());
357 }
358 
359 
360 ///
361 unittest
362 {
363     import std.datetime : SysTime;
364 
365     static class Address {
366         long zipcode = 42_420;
367         string city = "London";
368     }
369 
370     static class User {
371         Address address = new Address();
372         string name = "Foo";
373         string lastName = "Bar";
374         string mail = "foo.bar@baz.fr";
375         long timestamp;
376     }
377 
378     static class UserDTO {
379         string fullName;
380         string email;
381         string addressCity;
382         long   addressZipcode;
383         SysTime timestamp;
384         int context;
385     }
386 
387     // we would like to map from User to UserDTO
388     auto am = MapperConfiguration!(
389         // create a type converter for a long to SysTime
390         CreateMap!(long, SysTime)
391             .ConvertUsing!((long ts) => SysTime(ts)),
392         // create a mapping for User to UserDTO
393         CreateMap!(User, UserDTO)
394             // map member using a delegate
395             .ForMember!("fullName", (User a) => a.name ~ " " ~ a.lastName )
396             // map UserDTO.email to User.mail
397             .ForMember!("email", "mail")
398             // ignore UserDTO.context
399             .Ignore!"context")
400             // other member are automatically mapped
401             .createMapper();
402 
403     auto user = new User();
404     const UserDTO dto = am.map!UserDTO(user);
405 
406     assert(dto.fullName == user.name ~ " " ~ user.lastName);
407     assert(dto.addressCity == user.address.city);
408     assert(dto.addressZipcode == user.address.zipcode);
409 }
410 
411 /// Naming conventions
412 unittest
413 {
414     static class A {
415         int foo_bar_baz = 42;
416         string data_processor = "42";
417     }
418 
419     static class B {
420         int fooBarBaz;
421         string dataProcessor;
422     }
423 
424     auto am = MapperConfiguration!(
425         CreateMap!(A, B)
426             .SourceMemberNaming!LowerUnderscoreNamingConvention
427             .DestMemberNaming!CamelCaseNamingConvention)
428                 .createMapper();
429 
430     A a = new A();
431     const B b = am.map!B(a);
432     assert(a.foo_bar_baz == b.fooBarBaz);
433     assert(a.data_processor == b.dataProcessor);
434 }
435 
436 /// Type converters
437 unittest
438 {
439     import std.datetime: SysTime;
440 
441     static class A {
442         long timestamp = 1_542_873_605;
443     }
444 
445     static class B {
446         SysTime timestamp;
447     }
448 
449     auto am_delegate = MapperConfiguration!(
450         CreateMap!(long, SysTime).ConvertUsing!((long ts) => SysTime(ts)),
451         CreateMap!(A, B))
452             .createMapper();
453 
454     A a = new A();
455     B b = am_delegate.map!B(a);
456     assert(SysTime(a.timestamp) == b.timestamp);
457 
458 
459     static class TimestampToSystime : ITypeConverter!(long, SysTime) {
460         override SysTime convert(long ts) {
461             return SysTime(ts);
462         }
463     }
464 
465     auto am_class = MapperConfiguration!(
466         CreateMap!(long, SysTime).ConvertUsing!TimestampToSystime,
467         CreateMap!(A, B))
468             .createMapper();
469 
470     a = new A();
471     b = am_class.map!B(a);
472     assert(SysTime(a.timestamp) == b.timestamp);
473 }
474 
475 /// struct
476 unittest
477 {
478     static struct A {
479         int foo;
480     }
481 
482     static struct B {
483         int foo;
484     }
485 
486     auto am = MapperConfiguration!(
487         CreateMap!(A, B))
488             .createMapper();
489 
490     A a;
491     const B b = am.map!B(a);
492     assert(b.foo == a.foo);
493 }
494 
495 /// reverse flattening
496 unittest
497 {
498     static class Address {
499         int zipcode;
500     }
501 
502     static class A {
503         Address address;
504     }
505 
506     static class B {
507         int addressZipcode = 74_000;
508     }
509 
510     auto am = MapperConfiguration!(
511         CreateMap!(A, B)
512             .ReverseMap!())
513             .createMapper();
514 
515     B b = new B();
516     const A a = am.map!A(b);
517     assert(b.addressZipcode == a.address.zipcode);
518 }
519 
520 /// array
521 unittest
522 {
523     import std.algorithm.comparison : equal;
524 
525     static class Data {
526         this() {}
527         this(string id) { this.id = id; }
528         string id;
529     }
530 
531     static class DataDTO {
532         string id;
533     }
534 
535     static class A {
536         int[] foo = [1, 2, 4, 8];
537         Data[] data = [new Data("baz"), new Data("foz")];
538     }
539 
540     static class B {
541         int[] foo;
542         DataDTO[] data;
543     }
544 
545     auto am = MapperConfiguration!(
546         CreateMap!(Data, DataDTO),
547         CreateMap!(A, B))
548             .createMapper();
549 
550     MapperConfiguration!(
551         CreateMap!(Data, DataDTO),
552         CreateMap!(A, B))
553             .createMapper();
554 
555     A a = new A();
556     B b = am.map!B(a);
557 
558     assert(b.foo.equal(a.foo));
559     assert(b.data.length == 2);
560     assert(b.data[0].id == "baz");
561     assert(b.data[1].id == "foz");
562 }
563 
564 /// auto
565 unittest
566 {
567     static class Address {
568         long zipcode = 74_000;
569         string city = "unknown";
570     }
571 
572     static class User {
573         Address address = new Address();
574         string name = "Eliott";
575         string lastName = "Dumeix";
576     }
577 
578     static class UserDTO {
579         string fullName;
580         string addressCity;
581         long   addressZipcode;
582     }
583 
584     auto am = MapperConfiguration!(
585         CreateMap!(User, UserDTO)
586             .ForMember!("fullName", (User a) => a.name ~ " " ~ a.lastName ))
587             .createMapper();
588 
589     auto user = new User();
590     const UserDTO dto = am.map!UserDTO(user);
591 
592     assert(dto.fullName == user.name ~ " " ~ user.lastName);
593     assert(dto.addressCity == user.address.city);
594     assert(dto.addressZipcode == user.address.zipcode);
595 
596 }
597 
598 /// flattening
599 unittest
600 {
601     static class Address {
602         int zipcode = 74_000;
603     }
604 
605     static class A {
606         Address address = new Address();
607     }
608 
609     static class B {
610         int addressZipcode;
611     }
612 
613     auto am = MapperConfiguration!(
614         CreateMap!(A, B)
615             .ForMember!("addressZipcode", "address.zipcode"))
616             .createMapper();
617 
618     A a = new A();
619     const B b = am.map!B(a);
620     assert(b.addressZipcode == a.address.zipcode);
621 }
622 
623 /// nest
624 unittest
625 {
626     static class Address {
627         int zipcode = 74_000;
628     }
629 
630     static class AddressDTO {
631         int zipcode;
632     }
633 
634     static class A {
635         Address address = new Address();
636     }
637 
638     static class B {
639         AddressDTO address;
640     }
641 
642     auto am = MapperConfiguration!(
643         CreateMap!(Address, AddressDTO)
644             .ForMember!("zipcode", "zipcode")
645             .ReverseMap!(),
646         CreateMap!(A, B)
647             .ForMember!("address", "address")
648             .ReverseMap!())
649                 .createMapper();
650 
651     A a = new A();
652     const B b = am.map!B(a);
653     assert(b.address.zipcode == a.address.zipcode);
654 
655     // test reversed mapper
656     am.map!Address(new AddressDTO());
657 }
658 
659 unittest
660 {
661     static class A {
662         string str = "foo";
663         int foo = 42;
664     }
665 
666     static class B {
667         string str;
668         int foo;
669         long bar;
670         string mod;
671     }
672 
673    auto am = MapperConfiguration!(
674         CreateMap!(A, B)
675             .ForMember!("str", "str")
676             .ForMember!("foo", "foo")
677             .Ignore!"bar"
678             .ForMember!("mod", (A a) {
679                 return "modified";
680             }))
681             .createMapper();
682 
683 
684     A a = new A();
685     const B b = am.map!B(a);
686     assert(b.str == a.str);
687     assert(a.foo == a.foo);
688     assert(b.mod == "modified");
689 }