1 module boilerplate.builder; 2 3 import std.typecons : Nullable, Tuple; 4 5 private alias Info = Tuple!(string, "typeField", string, "builderField"); 6 7 public alias Builder(T) = typeof(T.Builder()); 8 9 public mixin template BuilderImpl(T, Info = Info, alias BuilderProxy = BuilderProxy, alias _toInfo = _toInfo) 10 { 11 import boilerplate.util : Optional, optionallyRemoveTrailingUnderline, removeTrailingUnderline; 12 static import std.algorithm; 13 static import std.format; 14 static import std.meta; 15 static import std.range; 16 static import std.typecons; 17 18 static assert(__traits(hasMember, T, "ConstructorInfo")); 19 20 static if (T.ConstructorInfo.fields.length > 0) 21 { 22 private enum string[] builderFields = [ 23 std.meta.staticMap!(optionallyRemoveTrailingUnderline, 24 std.meta.aliasSeqOf!(T.ConstructorInfo.fields))]; 25 } 26 else 27 { 28 private enum string[] builderFields = []; 29 } 30 private enum fieldInfoList = std.range.zip(T.ConstructorInfo.fields, builderFields); 31 32 private template BuilderFieldInfo(string member) 33 { 34 mixin(std.format.format!q{alias FieldType = T.ConstructorInfo.FieldInfo.%s.Type;}(member)); 35 36 import std.typecons : Nullable; 37 38 static if (is(FieldType : Nullable!Arg, Arg)) 39 { 40 alias BaseType = Arg; 41 } 42 else 43 { 44 alias BaseType = FieldType; 45 } 46 47 // type has a builder ... that constructs it 48 // protects from such IDIOTIC DESIGN ERRORS as `alias Nullable!T.get this` 49 // NOTE: can't use hasMember because https://issues.dlang.org/show_bug.cgi?id=13269 50 static if (__traits(compiles, BaseType.Builder())) 51 { 52 alias BuilderResultType = typeof(BaseType.Builder().builderValue); 53 54 static if (is(BuilderResultType: BaseType)) 55 { 56 alias Type = BuilderProxy!FieldType; 57 enum isBuildable = true; 58 } 59 else 60 { 61 alias Type = Optional!FieldType; 62 enum isBuildable = false; 63 } 64 } 65 else 66 { 67 alias Type = Optional!FieldType; 68 enum isBuildable = false; 69 } 70 } 71 72 static foreach (typeField, builderField; fieldInfoList) 73 { 74 mixin(`public BuilderFieldInfo!typeField.Type ` ~ builderField ~ `;`); 75 } 76 77 public bool isValid() const 78 { 79 return this.getError().isNull; 80 } 81 82 public std.typecons.Nullable!string getError() const 83 { 84 alias Nullable = std.typecons.Nullable; 85 86 static foreach (typeField, builderField; fieldInfoList) 87 { 88 static if (BuilderFieldInfo!(typeField).isBuildable) 89 { 90 // if the proxy has never been used as a builder, 91 // ie. either a value was assigned or it was untouched 92 // then a default value may be used instead. 93 if (__traits(getMember, this, builderField)._isUnset) 94 { 95 static if (!__traits(getMember, T.ConstructorInfo.FieldInfo, typeField).useDefault) 96 { 97 return Nullable!string( 98 "required field '" ~ builderField ~ "' not set in builder of " ~ T.stringof); 99 } 100 } 101 else if (__traits(getMember, this, builderField)._isBuilder) 102 { 103 auto subError = __traits(getMember, this, builderField)._builder.getError; 104 105 if (!subError.isNull) 106 { 107 return Nullable!string(subError.get ~ " of " ~ T.stringof); 108 } 109 } 110 // else it carries a full value. 111 } 112 else 113 { 114 static if (!__traits(getMember, T.ConstructorInfo.FieldInfo, typeField).useDefault) 115 { 116 if (__traits(getMember, this, builderField).isNull) 117 { 118 return Nullable!string( 119 "required field '" ~ builderField ~ "' not set in builder of " ~ T.stringof); 120 } 121 } 122 } 123 } 124 return Nullable!string(); 125 } 126 127 public @property T builderValue(size_t line = __LINE__, string file = __FILE__) 128 in 129 { 130 import core.exception : AssertError; 131 132 if (!this.isValid) 133 { 134 throw new AssertError(this.getError.get, file, line); 135 } 136 } 137 do 138 { 139 auto getArg(string typeField, string builderField)() 140 { 141 static if (BuilderFieldInfo!(typeField).isBuildable) 142 { 143 if (__traits(getMember, this, builderField)._isBuilder) 144 { 145 return __traits(getMember, this, builderField)._builderValue; 146 } 147 else if (__traits(getMember, this, builderField)._isValue) 148 { 149 return __traits(getMember, this, builderField)._value; 150 } 151 else 152 { 153 assert(__traits(getMember, this, builderField)._isUnset); 154 155 static if (__traits(getMember, T.ConstructorInfo.FieldInfo, typeField).useDefault) 156 { 157 return __traits(getMember, T.ConstructorInfo.FieldInfo, typeField).fieldDefault; 158 } 159 else 160 { 161 assert(false, "isValid/build do not match 1"); 162 } 163 } 164 } 165 else 166 { 167 if (!__traits(getMember, this, builderField).isNull) 168 { 169 return __traits(getMember, this, builderField)._get; 170 } 171 else 172 { 173 static if (__traits(getMember, T.ConstructorInfo.FieldInfo, typeField).useDefault) 174 { 175 return __traits(getMember, T.ConstructorInfo.FieldInfo, typeField).fieldDefault; 176 } 177 else 178 { 179 assert(false, "isValid/build do not match 2"); 180 } 181 } 182 } 183 } 184 185 enum getArgArray = std.range.array( 186 std.algorithm.map!(i => std.format.format!`getArg!(fieldInfoList[%s][0], fieldInfoList[%s][1])`(i, i))( 187 std.range.iota(fieldInfoList.length))); 188 189 static if (is(T == class)) 190 { 191 return mixin(std.format.format!q{new T(%-(%s, %))}(getArgArray)); 192 } 193 else 194 { 195 return mixin(std.format.format!q{T(%-(%s, %))}(getArgArray)); 196 } 197 } 198 199 static foreach (aliasMember; __traits(getAliasThis, T)) 200 { 201 mixin(`alias ` ~ aliasMember ~ ` this;`); 202 } 203 204 static if (!std.algorithm.canFind( 205 std.algorithm.map!removeTrailingUnderline(T.ConstructorInfo.fields), 206 "value")) 207 { 208 public alias value = builderValue; 209 } 210 } 211 212 // value that is either a T, or a Builder for T. 213 // Used for nested builder initialization. 214 public struct BuilderProxy(T) 215 { 216 private enum Mode 217 { 218 unset, 219 builder, 220 value, 221 } 222 223 static if (is(T : Nullable!Arg, Arg)) 224 { 225 enum isNullable = true; 226 alias InnerType = Arg; 227 } 228 else 229 { 230 enum isNullable = false; 231 alias InnerType = T; 232 } 233 234 private union Data 235 { 236 T value; 237 238 Builder!InnerType builder; 239 240 this(inout(T) value) inout pure 241 { 242 this.value = value; 243 } 244 245 this(inout(Builder!InnerType) builder) inout pure 246 { 247 this.builder = builder; 248 } 249 } 250 251 struct DataWrapper 252 { 253 Data data; 254 } 255 256 private Mode mode = Mode.unset; 257 258 private DataWrapper wrapper = DataWrapper.init; 259 260 public this(T value) 261 { 262 opAssign(value); 263 } 264 265 public void opAssign(T value) 266 in 267 { 268 assert( 269 this.mode != Mode.builder, 270 "Builder: cannot set field by value since a subfield has already been set."); 271 } 272 do 273 { 274 import boilerplate.util : move, moveEmplace; 275 276 static if (isNullable) 277 { 278 DataWrapper newWrapper = DataWrapper(Data(value)); 279 } 280 else 281 { 282 DataWrapper newWrapper = DataWrapper(Data(value)); 283 } 284 285 if (this.mode == Mode.value) 286 { 287 move(newWrapper, this.wrapper); 288 } 289 else 290 { 291 moveEmplace(newWrapper, this.wrapper); 292 } 293 this.mode = Mode.value; 294 } 295 296 static if (isNullable) 297 { 298 public void opAssign(InnerType value) 299 { 300 return opAssign(T(value)); 301 } 302 } 303 304 public bool _isUnset() const 305 { 306 return this.mode == Mode.unset; 307 } 308 309 public bool _isValue() const 310 { 311 return this.mode == Mode.value; 312 } 313 314 public bool _isBuilder() const 315 { 316 return this.mode == Mode.builder; 317 } 318 319 public inout(T) _value() inout 320 in 321 { 322 assert(this.mode == Mode.value); 323 } 324 do 325 { 326 return this.wrapper.data.value; 327 } 328 329 public ref auto _builder() inout 330 in 331 { 332 assert(this.mode == Mode.builder); 333 } 334 do 335 { 336 return this.wrapper.data.builder; 337 } 338 339 public auto _builderValue() 340 in 341 { 342 assert(this.mode == Mode.builder); 343 } 344 do 345 { 346 static if (isNullable) 347 { 348 return T(this.wrapper.data.builder.builderValue); 349 } 350 else 351 { 352 return this.wrapper.data.builder.builderValue; 353 } 354 } 355 356 alias _implicitBuilder this; 357 358 public @property ref Builder!InnerType _implicitBuilder() 359 { 360 import boilerplate.util : move, moveEmplace; 361 362 if (this.mode == Mode.unset) 363 { 364 auto newWrapper = DataWrapper(Data(Builder!InnerType.init)); 365 366 this.mode = Mode.builder; 367 moveEmplace(newWrapper, this.wrapper); 368 } 369 else if (this.mode == Mode.value) 370 { 371 static if (isNullable) 372 { 373 assert( 374 !this.wrapper.data.value.isNull, 375 "Builder: cannot set sub-field directly since field was explicitly initialized to Nullable.null"); 376 auto value = this.wrapper.data.value.get; 377 } 378 else 379 { 380 auto value = this.wrapper.data.value; 381 } 382 static if (__traits(compiles, value.BuilderFrom())) 383 { 384 auto newWrapper = DataWrapper(Data(value.BuilderFrom())); 385 386 this.mode = Mode.builder; 387 move(newWrapper, this.wrapper); 388 } 389 else 390 { 391 assert( 392 false, 393 "Builder: cannot set sub-field directly since field is already being initialized by value " ~ 394 "(and BuilderFrom is unavailable in " ~ typeof(this.wrapper.data.value).stringof ~ ")"); 395 } 396 } 397 398 return this.wrapper.data.builder; 399 } 400 } 401 402 public Info _toInfo(Tuple!(string, string) pair) 403 { 404 return Info(pair[0], pair[1]); 405 }