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 48 static if (__traits(compiles, BaseType.Builder())) 49 { 50 alias Type = BuilderProxy!FieldType; 51 enum isBuildable = true; 52 } 53 else static if (is(FieldType == E[], E)) 54 { 55 static if (__traits(compiles, E.Builder())) 56 { 57 alias Type = BuilderProxy!FieldType; 58 enum isBuildable = true; 59 } 60 else 61 { 62 alias Type = Optional!FieldType; 63 enum isBuildable = false; 64 } 65 } 66 else 67 { 68 alias Type = Optional!FieldType; 69 enum isBuildable = false; 70 } 71 } 72 73 static foreach (typeField, builderField; fieldInfoList) 74 { 75 mixin(`public BuilderFieldInfo!typeField.Type ` ~ builderField ~ `;`); 76 } 77 78 public bool isValid() const 79 { 80 return this.getError().isNull; 81 } 82 83 public std.typecons.Nullable!string getError() const 84 { 85 alias Nullable = std.typecons.Nullable; 86 87 static foreach (typeField, builderField; fieldInfoList) 88 { 89 static if (BuilderFieldInfo!(typeField).isBuildable) 90 { 91 // if the proxy has never been used as a builder, 92 // ie. either a value was assigned or it was untouched 93 // then a default value may be used instead. 94 if (__traits(getMember, this, builderField)._isUnset) 95 { 96 static if (!__traits(getMember, T.ConstructorInfo.FieldInfo, typeField).useDefault) 97 { 98 return Nullable!string( 99 "required field '" ~ builderField ~ "' not set in builder of " ~ T.stringof); 100 } 101 } 102 else if (__traits(getMember, this, builderField)._isBuilder) 103 { 104 auto subError = __traits(getMember, this, builderField)._builder.getError; 105 106 if (!subError.isNull) 107 { 108 return Nullable!string(subError.get ~ " of " ~ T.stringof); 109 } 110 } 111 // else it carries a full value. 112 } 113 else 114 { 115 static if (!__traits(getMember, T.ConstructorInfo.FieldInfo, typeField).useDefault) 116 { 117 if (__traits(getMember, this, builderField).isNull) 118 { 119 return Nullable!string( 120 "required field '" ~ builderField ~ "' not set in builder of " ~ T.stringof); 121 } 122 } 123 } 124 } 125 return Nullable!string(); 126 } 127 128 public @property T builderValue(size_t line = __LINE__, string file = __FILE__) 129 in 130 { 131 import core.exception : AssertError; 132 133 if (!this.isValid) 134 { 135 throw new AssertError(this.getError.get, file, line); 136 } 137 } 138 do 139 { 140 auto getArg(string typeField, string builderField)() 141 { 142 static if (BuilderFieldInfo!(typeField).isBuildable) 143 { 144 import std.meta : Alias; 145 146 alias Type = Alias!(__traits(getMember, T.ConstructorInfo.FieldInfo, typeField)).Type; 147 148 static if (is(Type == E[], E)) 149 { 150 if (__traits(getMember, this, builderField)._isArray) 151 { 152 return __traits(getMember, this, builderField)._arrayValue; 153 } 154 else if (__traits(getMember, this, builderField)._isValue) 155 { 156 return __traits(getMember, this, builderField)._value; 157 } 158 } 159 else 160 { 161 if (__traits(getMember, this, builderField)._isBuilder) 162 { 163 return __traits(getMember, this, builderField)._builderValue; 164 } 165 else if (__traits(getMember, this, builderField)._isValue) 166 { 167 return __traits(getMember, this, builderField)._value; 168 } 169 } 170 assert(__traits(getMember, this, builderField)._isUnset); 171 172 static if (__traits(getMember, T.ConstructorInfo.FieldInfo, typeField).useDefault) 173 { 174 return __traits(getMember, T.ConstructorInfo.FieldInfo, typeField).fieldDefault; 175 } 176 else 177 { 178 assert(false, "isValid/build do not match 1"); 179 } 180 } 181 else 182 { 183 if (!__traits(getMember, this, builderField).isNull) 184 { 185 return __traits(getMember, this, builderField)._get; 186 } 187 else 188 { 189 static if (__traits(getMember, T.ConstructorInfo.FieldInfo, typeField).useDefault) 190 { 191 return __traits(getMember, T.ConstructorInfo.FieldInfo, typeField).fieldDefault; 192 } 193 else 194 { 195 assert(false, "isValid/build do not match 2"); 196 } 197 } 198 } 199 } 200 201 enum getArgArray = std.range.array( 202 std.algorithm.map!(i => std.format.format!`getArg!(fieldInfoList[%s][0], fieldInfoList[%s][1])`(i, i))( 203 std.range.iota(fieldInfoList.length))); 204 205 static if (is(T == class)) 206 { 207 return mixin(std.format.format!q{new T(%-(%s, %))}(getArgArray)); 208 } 209 else 210 { 211 return mixin(std.format.format!q{T(%-(%s, %))}(getArgArray)); 212 } 213 } 214 215 static foreach (aliasMember; __traits(getAliasThis, T)) 216 { 217 mixin(`alias ` ~ optionallyRemoveTrailingUnderline!aliasMember ~ ` this;`); 218 } 219 220 static if (!std.algorithm.canFind( 221 std.algorithm.map!removeTrailingUnderline(T.ConstructorInfo.fields), 222 "value")) 223 { 224 public alias value = builderValue; 225 } 226 } 227 228 // value that is either a T, or a Builder for T. 229 // Used for nested builder initialization. 230 public struct BuilderProxy(T) 231 { 232 private enum Mode 233 { 234 unset, 235 builder, 236 value, 237 array, // array of builders 238 } 239 240 static if (is(T : Nullable!Arg, Arg)) 241 { 242 enum isNullable = true; 243 alias InnerType = Arg; 244 } 245 else 246 { 247 enum isNullable = false; 248 alias InnerType = T; 249 } 250 251 private union Data 252 { 253 T value; 254 255 this(inout(T) value) inout pure 256 { 257 this.value = value; 258 } 259 260 static if (is(T == E[], E)) 261 { 262 E.BuilderType!()[] array; 263 264 this(inout(E.BuilderType!())[] array) inout pure 265 { 266 this.array = array; 267 } 268 } 269 else 270 { 271 InnerType.BuilderType!() builder; 272 273 this(inout(InnerType.BuilderType!()) builder) inout pure 274 { 275 this.builder = builder; 276 } 277 } 278 } 279 280 struct DataWrapper 281 { 282 Data data; 283 } 284 285 private Mode mode = Mode.unset; 286 287 private DataWrapper wrapper = DataWrapper.init; 288 289 public this(T value) 290 { 291 opAssign(value); 292 } 293 294 public void opAssign(T value) 295 in(this.mode != Mode.builder, 296 "Builder: cannot set field by value since a subfield has already been set.") 297 { 298 import boilerplate.util : move, moveEmplace; 299 300 static if (isNullable) 301 { 302 DataWrapper newWrapper = DataWrapper(Data(value)); 303 } 304 else 305 { 306 DataWrapper newWrapper = DataWrapper(Data(value)); 307 } 308 309 if (this.mode == Mode.value) 310 { 311 move(newWrapper, this.wrapper); 312 } 313 else 314 { 315 moveEmplace(newWrapper, this.wrapper); 316 } 317 this.mode = Mode.value; 318 } 319 320 static if (isNullable) 321 { 322 public void opAssign(InnerType value) 323 { 324 return opAssign(T(value)); 325 } 326 } 327 328 public bool _isUnset() const 329 { 330 return this.mode == Mode.unset; 331 } 332 333 public bool _isValue() const 334 { 335 return this.mode == Mode.value; 336 } 337 338 public bool _isBuilder() const 339 { 340 return this.mode == Mode.builder; 341 } 342 343 public bool _isArray() const 344 { 345 return this.mode == Mode.array; 346 } 347 348 public inout(T) _value() inout 349 in (this.mode == Mode.value) 350 { 351 return this.wrapper.data.value; 352 } 353 354 public ref auto _builder() inout 355 in (this.mode == Mode.builder) 356 { 357 static if (is(T == E[], E)) 358 { 359 int i = 0; 360 361 assert(i != 0); // assert(false) but return stays "reachable" 362 return E.Builder(); 363 } 364 else 365 { 366 return this.wrapper.data.builder; 367 } 368 } 369 370 public auto _builderValue() 371 in (this.mode == Mode.builder) 372 { 373 static if (is(T == E[], E)) 374 { 375 int i = 0; 376 377 assert(i != 0); // assert(false) but return stays "reachable" 378 return E.Builder(); 379 } 380 else static if (isNullable) 381 { 382 return T(this.wrapper.data.builder.builderValue); 383 } 384 else 385 { 386 return this.wrapper.data.builder.builderValue; 387 } 388 } 389 390 public T _arrayValue() 391 in (this.mode == Mode.array) 392 { 393 import std.algorithm : map; 394 import std.array : array; 395 396 static if (is(T == E[], E)) 397 { 398 // enforce that E is the return value 399 static E builderValue(Element)(Element element) { return element.builderValue; } 400 401 return this.wrapper.data.array.map!builderValue.array; 402 } 403 else 404 { 405 assert(false); 406 } 407 } 408 409 static if (is(T == E[], E)) 410 { 411 public ref E.BuilderType!() opIndex(size_t index) return 412 in (this.mode == Mode.unset || this.mode == Mode.array, 413 "cannot build array for already initialized field") 414 { 415 import boilerplate.util : moveEmplace; 416 417 if (this.mode == Mode.unset) 418 { 419 auto newWrapper = DataWrapper(Data(new E.BuilderType!()[](index + 1))); 420 421 this.mode = Mode.array; 422 moveEmplace(newWrapper, this.wrapper); 423 } 424 else while (this.wrapper.data.array.length <= index) 425 { 426 this.wrapper.data.array ~= E.Builder(); 427 } 428 return this.wrapper.data.array[index]; 429 } 430 431 public void opOpAssign(string op, R)(R rhs) 432 if (op == "~") 433 in (this.mode == Mode.unset || this.mode == Mode.value, 434 "Builder cannot append to array already initialized by index") 435 { 436 if (this.mode == Mode.unset) 437 { 438 opAssign(null); 439 } 440 opAssign(this.wrapper.data.value ~ rhs); 441 } 442 } 443 else 444 { 445 public @property ref InnerType.BuilderType!() _implicitBuilder() 446 { 447 import boilerplate.util : move, moveEmplace; 448 449 if (this.mode == Mode.unset) 450 { 451 auto newWrapper = DataWrapper(Data(InnerType.BuilderType!().init)); 452 453 this.mode = Mode.builder; 454 moveEmplace(newWrapper, this.wrapper); 455 } 456 else if (this.mode == Mode.value) 457 { 458 static if (isNullable) 459 { 460 assert( 461 !this.wrapper.data.value.isNull, 462 "Builder: cannot set sub-field directly since field was explicitly " ~ 463 "initialized to Nullable.null"); 464 auto value = this.wrapper.data.value.get; 465 } 466 else 467 { 468 auto value = this.wrapper.data.value; 469 } 470 static if (__traits(compiles, value.BuilderFrom())) 471 { 472 auto newWrapper = DataWrapper(Data(value.BuilderFrom())); 473 474 this.mode = Mode.builder; 475 move(newWrapper, this.wrapper); 476 } 477 else 478 { 479 assert( 480 false, 481 "Builder: cannot set sub-field directly since field is already being initialized by value " ~ 482 "(and BuilderFrom is unavailable in " ~ typeof(this.wrapper.data.value).stringof ~ ")"); 483 } 484 } 485 486 return this.wrapper.data.builder; 487 } 488 489 alias _implicitBuilder this; 490 } 491 } 492 493 public Info _toInfo(Tuple!(string, string) pair) 494 { 495 return Info(pair[0], pair[1]); 496 }