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 }