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