1 /**
2     AutoMapper configuration.
3 */
4 module automapper.config;
5 
6 import automapper.meta;
7 import automapper.mapper;
8 import automapper.type.converter : isTypeConverter;
9 import automapper.value.transformer : isValueTransformer;
10 import automapper.naming : isNamingConvention;
11 
12 
13 /// Is the provided type a configuration object ?
14 package template isConfigurationObject(T)
15 {
16     enum bool isConfigurationObject = (
17         isObjectMapperConfig!T ||
18         isTypeConverter!T ||
19         isValueTransformer!T);
20 }
21 
22 /**
23     `ObjectMapper` configuration.
24     Params:
25         TSrc = The type of source object.
26         TDst = The type of destination object.
27         TSourceConv = The type of source naming convention to use.
28         TDestConv = The type of source naming convention to use.
29         Rev = true if we must reverse this mapper.
30         Mps = A list of member mapping specialization.
31 */
32 package class ObjectMapperConfig(TSrc, TDst, TSourceConv, TDestConv, bool Rev, Mps...) if
33     (isClassOrStruct!TSrc && isClassOrStruct!TDst &&
34     isNamingConvention!TSourceConv && isNamingConvention!TDestConv &&
35     allSatisfy!(isObjectMemberMappingConfig, Mps))
36 {
37     alias TSource = TSrc;
38     alias TDest = TDst;
39     alias Mappings = Mps;
40     alias Reverse = Rev;
41 }
42 
43 /// Tell if it's an `ObjectMapperConfig`
44 package template isObjectMapperConfig(T)
45 {
46     static if (is(T : ObjectMapperConfig!(TSource, TDest, TSourceConv, TDestConv, Reverse, Mappings),
47         TSource, TDest, TSourceConv, TDestConv, bool Reverse, Mappings))
48         enum isObjectMapperConfig = true;
49     else static if (is(T : ObjectMapperConfig!(TSource, TDest, TSourceConv, TDestConv, Reverse),
50         TSource, TDest, TSourceConv, TDestConv, bool Reverse))
51         enum isObjectMapperConfig = true;
52     else
53         enum isObjectMapperConfig = false;
54 }
55 
56 /// A struct that act like a flag to indicate that the mapper must be reversed.
57 package struct ReverseMapConfig
58 {
59     // do nothing
60 }
61 
62 /// Tell if its a `ReverseMapConfig`
63 package template isReverseMapConfig(T)
64 {
65     enum isReverseMapConfig = is(T : ReverseMapConfig);
66 }
67 
68 /// A struct to configure the naming convention used in the source object.
69 package struct SourceNamingConventionConfig(T) if (isNamingConvention!T)
70 {
71     alias Convention = T;
72 }
73 
74 /// Tell if its a `SourceNamingConventionConfig`
75 package template isSourceNamingConventionConfig(T)
76 {
77     enum isSourceNamingConventionConfig = is(T : SourceNamingConventionConfig!C, C);
78 }
79 
80 /// Precise the naming convention for dest object in a mapper
81 package struct DestNamingConventionConfig(T) if (isNamingConvention!T)
82 {
83     alias Convention = T;
84 }
85 
86 /// Tell if its a `DestNamingConventionConfig`
87 package template isDestNamingConventionConfig(T)
88 {
89     enum isDestNamingConventionConfig = is(T : DestNamingConventionConfig!C, C);
90 }
91 
92 /**
93     Base class for create a config about a member in an object mapper.
94     Template_Params:
95         T = The member to map in the destination object
96 */
97 package class ObjectMemberMappingConfig(string T)
98 {
99     enum string DestMember = T;
100 }
101 
102 /// Tell if its a `ObjectMemberMappingConfig`
103 package template isObjectMemberMappingConfig(T)
104 {
105     enum bool isObjectMemberMappingConfig = (is(T: ObjectMemberMappingConfig!BM, string BM));
106 }
107 
108 /// `ForMemberConfig` mapping type
109 package enum ForMemberConfigType
110 {
111     mapMember,  // map a member to another member
112     mapDelegate // map a member to a delegate
113 }
114 
115 /**
116     Used to configure object mapper. It tells to map a member from source object
117     to a member in dest object.
118     Params:
119         DestMember = The member name in the destination object
120         SourceMember = The member name in the source object or a custom delegate
121 **/
122 package class ForMemberConfig(string DestMember, string SourceMember) : ObjectMemberMappingConfig!(DestMember)
123 {
124     enum ForMemberConfigType Type = ForMemberConfigType.mapMember;
125     alias Action = SourceMember;
126 }
127 
128 /**
129     Used to configure object mapper. It tells to map a member from source object
130     to a user-defined delegate.
131     Params:
132         DestMember = The member name in the destination object
133         Delegate = A `DestMemberType delegate(TSource)`
134 **/
135 package class ForMemberConfig(string DestMember, alias Delegate) : ObjectMemberMappingConfig!(DestMember)
136     if (isCallable!Delegate)
137 {
138     enum ForMemberConfigType Type = ForMemberConfigType.mapDelegate;
139     alias Action = Delegate;
140 }
141 
142 package template isForMember(T, ForMemberConfigType Type)
143 {
144     static if (is(T == ForMemberConfig!(DestMember, Action), string DestMember, alias Action))
145         static if (T.Type == Type)
146             enum bool isForMember = true;
147         else
148             enum bool isForMember = false;
149     else
150         enum bool isForMember = false;
151 }
152 
153 /**
154     Used to ignore a member in the destination object.
155     Template_Params:
156         T = The member name in the destination object
157 **/
158 package class IgnoreConfig(string T) : ObjectMemberMappingConfig!(T)
159 {
160     // do nothing
161 }
162 
163 /**
164     Define AutoMapper configuration.
165 */
166 class MapperConfiguration(Configs...) // if (allSatisfy!(isConfigurationObject, Configs))
167 {
168     // sort configuration object
169     alias ObjectMappersConfig = Filter!(isObjectMapperConfig, Configs);
170     alias TypesConverters = Filter!(isTypeConverter, Configs);
171     alias ValueTransformers = Filter!(isValueTransformer, Configs);
172 
173     ///
174     static auto createMapper()
175     {
176         import automapper;
177         return new AutoMapper!(typeof(this))();
178     }
179 }
180 
181 ///
182 unittest
183 {
184     import automapper;
185     import std.datetime : SysTime;
186 
187     static class Address {
188         long zipcode = 42_420;
189         string city = "London";
190     }
191 
192     static class User {
193         Address address = new Address();
194         string name = "Foo";
195         string lastName = "Bar";
196         string mail = "foo.bar@baz.fr";
197         long timestamp;
198     }
199 
200     static class UserDTO {
201         string fullName;
202         string email;
203         string addressCity;
204         long   addressZipcode;
205         SysTime timestamp;
206         int context;
207     }
208 
209     alias MyConfig = MapperConfiguration!(
210         // create a type converter for a long to SysTime
211         CreateMap!(long, SysTime)
212             .ConvertUsing!((long ts) => SysTime(ts)),
213         // create a mapping for User to UserDTO
214         CreateMap!(User, UserDTO)
215             // map member using a delegate
216             .ForMember!("fullName", (User a) => a.name ~ " " ~ a.lastName )
217             // map UserDTO.email to User.mail
218             .ForMember!("email", "mail")
219             // ignore UserDTO.context
220             .Ignore!"context");
221             // other member are automatically mapped
222 
223     auto am = MyConfig.createMapper();
224 
225     auto user = new User();
226     const UserDTO dto = am.map!UserDTO(user);
227 
228     assert(dto.fullName == user.name ~ " " ~ user.lastName);
229     assert(dto.addressCity == user.address.city);
230     assert(dto.addressZipcode == user.address.zipcode);
231 }