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 }