1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 20 /** 21 * Code generation metadata and templates used for implementing struct 22 * serialization. 23 * 24 * Many templates can be customized using field meta data, which is read from 25 * a manifest constant member of the given type called fieldMeta (if present), 26 * and is concatenated with the elements from the optional fieldMetaData 27 * template alias parameter. 28 * 29 * Some code generation templates take account of the optional TVerboseCodegen 30 * version declaration, which causes warning messages to be emitted if no 31 * metadata for a field/method has been found and the default behavior is 32 * used instead. If this version is not defined, the templates just silently 33 * behave like the Thrift compiler does in this situation, i.e. automatically 34 * assign negative ids (starting at -1) for fields and assume TReq.AUTO as 35 * requirement level. 36 */ 37 // Implementation note: All the templates in here taking a field metadata 38 // parameter should ideally have a constraint that restricts the alias to 39 // TFieldMeta[]-typed values, but the is() expressions seems to always fail. 40 module thrift.codegen.base; 41 42 import std.algorithm : find; 43 import std.array : empty, front; 44 import std.conv : to; 45 import std.exception : enforce; 46 import std.traits : BaseTypeTuple, isPointer, isSomeFunction, PointerTarget, 47 ReturnType; 48 import thrift.base; 49 import thrift.internal.codegen; 50 import thrift.protocol.base; 51 import thrift.util.hashset; 52 53 /* 54 * Thrift struct/service meta data, which is used to store information from 55 * the interface definition files not representable in plain D, i.e. field 56 * requirement levels, Thrift field IDs, etc. 57 */ 58 59 /** 60 * Struct field requirement levels. 61 */ 62 enum TReq { 63 /// Detect the requiredness from the field type: if it is nullable, treat 64 /// the field as optional, if it is non-nullable, treat the field as 65 /// required. This is the default used for handling structs not generated 66 /// from an IDL file, and never emitted by the Thrift compiler. TReq.AUTO 67 /// shouldn't be specified explicitly. 68 // Implementation note: thrift.codegen templates use 69 // thrift.internal.codegen.memberReq to resolve AUTO to REQUIRED/OPTIONAL 70 // instead of handling it directly. 71 AUTO, 72 73 /// The field is treated as optional when deserializing/receiving the struct 74 /// and as required when serializing/sending. This is the Thrift default if 75 /// neither "required" nor "optional" are specified in the IDL file. 76 OPT_IN_REQ_OUT, 77 78 /// The field is optional. 79 OPTIONAL, 80 81 /// The field is required. 82 REQUIRED, 83 84 /// Ignore the struct field when serializing/deserializing. 85 IGNORE 86 } 87 88 /** 89 * The way how methods are called. 90 */ 91 enum TMethodType { 92 /// Called in the normal two-way scheme consisting of a request and a 93 /// response. 94 REGULAR, 95 96 /// A fire-and-forget one-way method, where no response is sent and the 97 /// client immediately returns. 98 ONEWAY 99 } 100 101 /** 102 * Compile-time metadata for a struct field. 103 */ 104 struct TFieldMeta { 105 /// The name of the field. Used for matching a TFieldMeta with the actual 106 /// D struct member during code generation. 107 string name; 108 109 /// The (Thrift) id of the field. 110 short id; 111 112 /// Whether the field is requried. 113 TReq req; 114 115 /// A code string containing a D expression for the default value, if there 116 /// is one. 117 string defaultValue; 118 } 119 120 /** 121 * Compile-time metadata for a service method. 122 */ 123 struct TMethodMeta { 124 /// The name of the method. Used for matching a TMethodMeta with the actual 125 /// method during code generation. 126 string name; 127 128 /// Meta information for the parameteres. 129 TParamMeta[] params; 130 131 /// Specifies which exceptions can be thrown by the method. All other 132 /// exceptions are converted to a TApplicationException instead. 133 TExceptionMeta[] exceptions; 134 135 /// The fundamental type of the method. 136 TMethodType type; 137 } 138 139 /** 140 * Compile-time metadata for a service method parameter. 141 */ 142 struct TParamMeta { 143 /// The name of the parameter. Contrary to TFieldMeta, it only serves 144 /// decorative purposes here. 145 string name; 146 147 /// The Thrift id of the parameter in the param struct. 148 short id; 149 150 /// A code string containing a D expression for the default value for the 151 /// parameter, if any. 152 string defaultValue; 153 } 154 155 /** 156 * Compile-time metadata for a service method exception annotation. 157 */ 158 struct TExceptionMeta { 159 /// The name of the exception »return value«. Contrary to TFieldMeta, it 160 /// only serves decorative purposes here, as it is only used in code not 161 /// visible to processor implementations/service clients. 162 string name; 163 164 /// The Thrift id of the exception field in the return value struct. 165 short id; 166 167 /// The name of the exception type. 168 string type; 169 } 170 171 /** 172 * A pair of two TPorotocols. To be used in places where a list of protocols 173 * is expected, for specifying different protocols for input and output. 174 */ 175 struct TProtocolPair(InputProtocol, OutputProtocol) if ( 176 isTProtocol!InputProtocol && isTProtocol!OutputProtocol 177 ) {} 178 179 /** 180 * true if T is a TProtocolPair. 181 */ 182 template isTProtocolPair(T) { 183 static if (is(T _ == TProtocolPair!(I, O), I, O)) { 184 enum isTProtocolPair = true; 185 } else { 186 enum isTProtocolPair = false; 187 } 188 } 189 190 unittest { 191 static assert(isTProtocolPair!(TProtocolPair!(TProtocol, TProtocol))); 192 static assert(!isTProtocolPair!TProtocol); 193 } 194 195 /** 196 * true if T is a TProtocol or a TProtocolPair. 197 */ 198 template isTProtocolOrPair(T) { 199 enum isTProtocolOrPair = isTProtocol!T || isTProtocolPair!T; 200 } 201 202 unittest { 203 static assert(isTProtocolOrPair!TProtocol); 204 static assert(isTProtocolOrPair!(TProtocolPair!(TProtocol, TProtocol))); 205 static assert(!isTProtocolOrPair!void); 206 } 207 208 /** 209 * true if T represents a Thrift service. 210 */ 211 template isService(T) { 212 enum isService = isBaseService!T || isDerivedService!T; 213 } 214 215 /** 216 * true if T represents a Thrift service not derived from another service. 217 */ 218 template isBaseService(T) { 219 static if(is(T _ == interface) && 220 (!is(T TBases == super) || TBases.length == 0) 221 ) { 222 enum isBaseService = true; 223 } else { 224 enum isBaseService = false; 225 } 226 } 227 228 /** 229 * true if T represents a Thrift service derived from another service. 230 */ 231 template isDerivedService(T) { 232 static if(is(T _ == interface) && 233 is(T TBases == super) && TBases.length == 1 234 ) { 235 enum isDerivedService = isService!(TBases[0]); 236 } else { 237 enum isDerivedService = false; 238 } 239 } 240 241 /** 242 * For derived services, gets the base service interface. 243 */ 244 template BaseService(T) if (isDerivedService!T) { 245 alias BaseTypeTuple!T[0] BaseService; 246 } 247 248 249 /* 250 * Code generation templates. 251 */ 252 253 /** 254 * Mixin template defining additional helper methods for using a struct with 255 * Thrift, and a member called isSetFlags if the struct contains any fields 256 * for which an »is set« flag is needed. 257 * 258 * It can only be used inside structs or Exception classes. 259 * 260 * For example, consider the following struct definition: 261 * --- 262 * struct Foo { 263 * string a; 264 * int b; 265 * int c; 266 * 267 * mixin TStructHelpers!([ 268 * TFieldMeta("a", 1), // Implicitly optional (nullable). 269 * TFieldMeta("b", 2), // Implicitly required (non-nullable). 270 * TFieldMeta("c", 3, TReq.REQUIRED, "4") 271 * ]); 272 * } 273 * --- 274 * 275 * TStructHelper adds the following methods to the struct: 276 * --- 277 * /++ 278 * + Sets member fieldName to the given value and marks it as set. 279 * + 280 * + Examples: 281 * + --- 282 * + auto f = Foo(); 283 * + f.set!"b"(12345); 284 * + assert(f.isSet!"b"); 285 * + --- 286 * +/ 287 * void set(string fieldName)(MemberType!(This, fieldName) value); 288 * 289 * /++ 290 * + Resets member fieldName to the init property of its type and marks it as 291 * + not set. 292 * + 293 * + Examples: 294 * + --- 295 * + // Set f.b to some value. 296 * + auto f = Foo(); 297 * + f.set!"b"(12345); 298 * + 299 * + f.unset!b(); 300 * + 301 * + // f.b is now unset again. 302 * + assert(!f.isSet!"b"); 303 * + --- 304 * +/ 305 * void unset(string fieldName)(); 306 * 307 * /++ 308 * + Returns whether member fieldName is set. 309 * + 310 * + Examples: 311 * + --- 312 * + auto f = Foo(); 313 * + assert(!f.isSet!"b"); 314 * + f.set!"b"(12345); 315 * + assert(f.isSet!"b"); 316 * + --- 317 * +/ 318 * bool isSet(string fieldName)() const @property; 319 * 320 * /++ 321 * + Returns a string representation of the struct. 322 * + 323 * + Examples: 324 * + --- 325 * + auto f = Foo(); 326 * + f.a = "a string"; 327 * + assert(f.toString() == `Foo("a string", 0 (unset), 4)`); 328 * + --- 329 * +/ 330 * string toString() const; 331 * 332 * /++ 333 * + Deserializes the struct, setting its members to the values read from the 334 * + protocol. Forwards to readStruct(this, proto); 335 * +/ 336 * void read(Protocol)(Protocol proto) if (isTProtocol!Protocol); 337 * 338 * /++ 339 * + Serializes the struct to the target protocol. Forwards to 340 * + writeStruct(this, proto); 341 * +/ 342 * void write(Protocol)(Protocol proto) const if (isTProtocol!Protocol); 343 * --- 344 * 345 * Additionally, an opEquals() implementation is provided which simply 346 * compares all fields, but disregards the is set struct, if any (the exact 347 * signature obviously differs between structs and exception classes). The 348 * metadata is stored in a manifest constant called fieldMeta. 349 * 350 * Note: To set the default values for fields where one has been specified in 351 * the field metadata, a parameterless static opCall is generated, because D 352 * does not allow parameterless (default) constructors for structs. Thus, be 353 * always to use to initialize structs: 354 * --- 355 * Foo foo; // Wrong! 356 * auto foo = Foo(); // Correct. 357 * --- 358 */ 359 mixin template TStructHelpers(alias fieldMetaData = cast(TFieldMeta[])null) if ( 360 is(typeof(fieldMetaData) : TFieldMeta[]) 361 ) { 362 import std.algorithm : any; 363 import thrift.codegen.base; 364 import thrift.internal.codegen : isNullable, MemberType, mergeFieldMeta, 365 FieldNames; 366 import thrift.protocol.base : TProtocol, isTProtocol; 367 368 alias typeof(this) This; 369 static assert(is(This == struct) || is(This : Exception), 370 "TStructHelpers can only be used inside a struct or an Exception class."); 371 372 static if (TIsSetFlags!(This, fieldMetaData).tupleof.length > 0) { 373 // If we need to keep isSet flags around, create an instance of the 374 // container struct. 375 TIsSetFlags!(This, fieldMetaData) isSetFlags; 376 enum fieldMeta = fieldMetaData ~ [TFieldMeta("isSetFlags", 0, TReq.IGNORE)]; 377 } else { 378 enum fieldMeta = fieldMetaData; 379 } 380 381 void set(string fieldName)(MemberType!(This, fieldName) value) if ( 382 is(MemberType!(This, fieldName)) 383 ) { 384 __traits(getMember, this, fieldName) = value; 385 static if (is(typeof(mixin("this.isSetFlags." ~ fieldName)) : bool)) { 386 __traits(getMember, this.isSetFlags, fieldName) = true; 387 } 388 } 389 390 void unset(string fieldName)() if (is(MemberType!(This, fieldName))) { 391 static if (is(typeof(mixin("this.isSetFlags." ~ fieldName)) : bool)) { 392 __traits(getMember, this.isSetFlags, fieldName) = false; 393 } 394 __traits(getMember, this, fieldName) = MemberType!(This, fieldName).init; 395 } 396 397 bool isSet(string fieldName)() const @property if ( 398 is(MemberType!(This, fieldName)) 399 ) { 400 static if (isNullable!(MemberType!(This, fieldName))) { 401 return __traits(getMember, this, fieldName) !is null; 402 } else static if (is(typeof(mixin("this.isSetFlags." ~ fieldName)) : bool)) { 403 return __traits(getMember, this.isSetFlags, fieldName); 404 } else { 405 // This is a required field, which is always set. 406 return true; 407 } 408 } 409 410 static if (is(This _ == class)) { 411 override string toString() const { 412 return thriftToStringImpl(); 413 } 414 415 override bool opEquals(Object other) const { 416 auto rhs = cast(This)other; 417 if (rhs) { 418 return thriftOpEqualsImpl(rhs); 419 } 420 421 return (cast()super).opEquals(other); 422 } 423 424 override size_t toHash() const { 425 return thriftToHashImpl(); 426 } 427 } else { 428 string toString() const { 429 return thriftToStringImpl(); 430 } 431 432 bool opEquals(ref const This other) const { 433 return thriftOpEqualsImpl(other); 434 } 435 436 size_t toHash() const @safe nothrow { 437 return thriftToHashImpl(); 438 } 439 } 440 441 private string thriftToStringImpl() const { 442 import std.conv : to; 443 string result = This.stringof ~ "("; 444 mixin({ 445 string code = ""; 446 bool first = true; 447 foreach (name; FieldNames!(This, fieldMeta)) { 448 if (first) { 449 first = false; 450 } else { 451 code ~= "result ~= `, `;\n"; 452 } 453 code ~= "result ~= `" ~ name ~ ": ` ~ to!string(cast()this." ~ name ~ ");\n"; 454 code ~= "if (!isSet!q{" ~ name ~ "}) {\n"; 455 code ~= "result ~= ` (unset)`;\n"; 456 code ~= "}\n"; 457 } 458 return code; 459 }()); 460 result ~= ")"; 461 return result; 462 } 463 464 private bool thriftOpEqualsImpl(const ref This rhs) const { 465 foreach (name; FieldNames!This) { 466 if (mixin("this." ~ name) != mixin("rhs." ~ name)) return false; 467 } 468 return true; 469 } 470 471 private size_t thriftToHashImpl() const @trusted nothrow { 472 size_t hash = 0; 473 foreach (i, _; this.tupleof) { 474 auto val = this.tupleof[i]; 475 hash += typeid(val).getHash(&val); 476 } 477 return hash; 478 } 479 480 static if (any!`!a.defaultValue.empty`(mergeFieldMeta!(This, fieldMetaData))) { 481 static if (is(This _ == class)) { 482 this() { 483 mixin(thriftFieldInitCode!(mergeFieldMeta!(This, fieldMetaData))("this")); 484 } 485 } else { 486 // DMD @@BUG@@: Have to use auto here to avoid »no size yet for forward 487 // reference« errors. 488 static auto opCall() { 489 auto result = This.init; 490 mixin(thriftFieldInitCode!(mergeFieldMeta!(This, fieldMetaData))("result")); 491 return result; 492 } 493 } 494 } 495 496 void read(Protocol)(Protocol proto) if (isTProtocol!Protocol) { 497 // Need to explicitly specify fieldMetaData here, since it isn't already 498 // picked up in some situations (e.g. the TArgs struct for methods with 499 // multiple parameters in async_test_servers) otherwise. Due to a DMD 500 // @@BUG@@, we need to explicitly specify the other template parameters 501 // as well. 502 readStruct!(This, Protocol, fieldMetaData, false)(this, proto); 503 } 504 505 void write(Protocol)(Protocol proto) const if (isTProtocol!Protocol) { 506 writeStruct!(This, Protocol, fieldMetaData, false)(this, proto); 507 } 508 } 509 510 // DMD @@BUG@@: Having this inside TStructHelpers leads to weird lookup errors 511 // (e.g. for std.arry.empty). 512 string thriftFieldInitCode(alias fieldMeta)(string thisName) { 513 string code = ""; 514 foreach (field; fieldMeta) { 515 if (field.defaultValue.empty) continue; 516 code ~= thisName ~ "." ~ field.name ~ " = " ~ field.defaultValue ~ ";\n"; 517 } 518 return code; 519 } 520 521 unittest { 522 // Cannot make this nested in the unittest block due to a »no size yet for 523 // forward reference« error. 524 static struct Foo { 525 string a; 526 int b; 527 int c; 528 529 mixin TStructHelpers!([ 530 TFieldMeta("a", 1), 531 TFieldMeta("b", 2, TReq.OPT_IN_REQ_OUT), 532 TFieldMeta("c", 3, TReq.REQUIRED, "4") 533 ]); 534 } 535 536 auto f = Foo(); 537 538 f.set!"b"(12345); 539 assert(f.isSet!"b"); 540 f.unset!"b"(); 541 assert(!f.isSet!"b"); 542 f.set!"b"(12345); 543 assert(f.isSet!"b"); 544 f.unset!"b"(); 545 546 f.a = "a string"; 547 assert(f.toString() == `Foo(a: a string, b: 0 (unset), c: 4)`); 548 } 549 550 551 /** 552 * Generates an eponymous struct with boolean flags for the non-required 553 * non-nullable fields of T. 554 * 555 * Nullable fields are just set to null to signal »not set«, so no flag is 556 * emitted for them, even if they are optional. 557 * 558 * In most cases, you do not want to use this directly, but via TStructHelpers 559 * instead. 560 */ 561 template TIsSetFlags(T, alias fieldMetaData) { 562 mixin({ 563 string code = "struct TIsSetFlags {\n"; 564 foreach (meta; fieldMetaData) { 565 code ~= "static if (!is(MemberType!(T, `" ~ meta.name ~ "`))) {\n"; 566 code ~= q{ 567 static assert(false, "Field '" ~ meta.name ~ 568 "' referenced in metadata not present in struct '" ~ T.stringof ~ "'."); 569 }; 570 code ~= "}"; 571 if (meta.req == TReq.OPTIONAL || meta.req == TReq.OPT_IN_REQ_OUT) { 572 code ~= "else static if (!isNullable!(MemberType!(T, `" ~ meta.name ~ "`))) {\n"; 573 code ~= " bool " ~ meta.name ~ ";\n"; 574 code ~= "}\n"; 575 } 576 } 577 code ~= "}"; 578 return code; 579 }()); 580 } 581 582 /** 583 * Deserializes a Thrift struct from a protocol. 584 * 585 * Using the Protocol template parameter, the concrete TProtocol to use can be 586 * be specified. If the pointerStruct parameter is set to true, the struct 587 * fields are expected to be pointers to the actual data. This is used 588 * internally (combined with TPResultStruct) and usually should not be used in 589 * user code. 590 * 591 * This is a free function to make it possible to read exisiting structs from 592 * the wire without altering their definitions. 593 */ 594 void readStruct(T, Protocol, alias fieldMetaData = cast(TFieldMeta[])null, 595 bool pointerStruct = false)(auto ref T s, Protocol p) if (isTProtocol!Protocol) 596 { 597 mixin({ 598 string code; 599 600 // Check that all fields for which there is meta info are actually in the 601 // passed struct type. 602 foreach (field; mergeFieldMeta!(T, fieldMetaData)) { 603 code ~= "static assert(is(MemberType!(T, `" ~ field.name ~ "`)));\n"; 604 } 605 606 // Returns the code string for reading a value of type F off the wire and 607 // assigning it to v. The level parameter is used to make sure that there 608 // are no conflicting variable names on recursive calls. 609 string readValueCode(ValueType)(string v, size_t level = 0) { 610 // Some non-ambigous names to use (shadowing is not allowed in D). 611 immutable i = "i" ~ to!string(level); 612 immutable elem = "elem" ~ to!string(level); 613 immutable key = "key" ~ to!string(level); 614 immutable list = "list" ~ to!string(level); 615 immutable map = "map" ~ to!string(level); 616 immutable set = "set" ~ to!string(level); 617 immutable value = "value" ~ to!string(level); 618 619 alias FullyUnqual!ValueType F; 620 621 static if (is(F == bool)) { 622 return v ~ " = p.readBool();"; 623 } else static if (is(F == byte)) { 624 return v ~ " = p.readByte();"; 625 } else static if (is(F == double)) { 626 return v ~ " = p.readDouble();"; 627 } else static if (is(F == short)) { 628 return v ~ " = p.readI16();"; 629 } else static if (is(F == int)) { 630 return v ~ " = p.readI32();"; 631 } else static if (is(F == long)) { 632 return v ~ " = p.readI64();"; 633 } else static if (is(F : string)) { 634 return v ~ " = p.readString();"; 635 } else static if (is(F == enum)) { 636 return v ~ " = cast(typeof(" ~ v ~ "))p.readI32();"; 637 } else static if (is(F _ : E[], E)) { 638 return "{\n" ~ 639 "auto " ~ list ~ " = p.readListBegin();\n" ~ 640 // TODO: Check element type here? 641 v ~ " = new typeof(" ~ v ~ "[0])[" ~ list ~ ".size];\n" ~ 642 "foreach (" ~ i ~ "; 0 .. " ~ list ~ ".size) {\n" ~ 643 readValueCode!E(v ~ "[" ~ i ~ "]", level + 1) ~ "\n" ~ 644 "}\n" ~ 645 "p.readListEnd();\n" ~ 646 "}"; 647 } else static if (is(F _ : V[K], K, V)) { 648 return "{\n" ~ 649 "auto " ~ map ~ " = p.readMapBegin();" ~ 650 v ~ " = null;\n" ~ 651 // TODO: Check key/value types here? 652 "foreach (" ~ i ~ "; 0 .. " ~ map ~ ".size) {\n" ~ 653 "FullyUnqual!(typeof(" ~ v ~ ".keys[0])) " ~ key ~ ";\n" ~ 654 readValueCode!K(key, level + 1) ~ "\n" ~ 655 "typeof(" ~ v ~ ".values[0]) " ~ value ~ ";\n" ~ 656 readValueCode!V(value, level + 1) ~ "\n" ~ 657 v ~ "[cast(typeof(" ~ v ~ ".keys[0]))" ~ key ~ "] = " ~ value ~ ";\n" ~ 658 "}\n" ~ 659 "p.readMapEnd();" ~ 660 "}"; 661 } else static if (is(F _ : HashSet!(E), E)) { 662 return "{\n" ~ 663 "auto " ~ set ~ " = p.readSetBegin();" ~ 664 // TODO: Check element type here? 665 v ~ " = new typeof(" ~ v ~ ")();\n" ~ 666 "foreach (" ~ i ~ "; 0 .. " ~ set ~ ".size) {\n" ~ 667 "typeof(" ~ v ~ "[][0]) " ~ elem ~ ";\n" ~ 668 readValueCode!E(elem, level + 1) ~ "\n" ~ 669 v ~ " ~= " ~ elem ~ ";\n" ~ 670 "}\n" ~ 671 "p.readSetEnd();" ~ 672 "}"; 673 } else static if (is(F == struct) || is(F : TException)) { 674 static if (is(F == struct)) { 675 auto result = v ~ " = typeof(" ~ v ~ ")();\n"; 676 } else { 677 auto result = v ~ " = new typeof(" ~ v ~ ")();\n"; 678 } 679 680 static if (__traits(compiles, F.init.read(TProtocol.init))) { 681 result ~= v ~ ".read(p);"; 682 } else { 683 result ~= "readStruct(" ~ v ~ ", p);"; 684 } 685 return result; 686 } else { 687 static assert(false, "Cannot represent type in Thrift: " ~ F.stringof); 688 } 689 } 690 691 string readFieldCode(FieldType)(string name, short id, TReq req) { 692 static if (pointerStruct && isPointer!FieldType) { 693 immutable v = "(*s." ~ name ~ ")"; 694 alias PointerTarget!FieldType F; 695 } else { 696 immutable v = "s." ~ name; 697 alias FieldType F; 698 } 699 700 string code = "case " ~ to!string(id) ~ ":\n"; 701 code ~= "if (f.type == " ~ dToTTypeString!F ~ ") {\n"; 702 code ~= readValueCode!F(v) ~ "\n"; 703 if (req == TReq.REQUIRED) { 704 // For required fields, set the corresponding local isSet variable. 705 code ~= "isSet_" ~ name ~ " = true;\n"; 706 } else if (!isNullable!F){ 707 code ~= "s.isSetFlags." ~ name ~ " = true;\n"; 708 } 709 code ~= "} else skip(p, f.type);\n"; 710 code ~= "break;\n"; 711 return code; 712 } 713 714 // Code for the local boolean flags used to make sure required fields have 715 // been found. 716 string isSetFlagCode = ""; 717 718 // Code for checking whether the flags for the required fields are true. 719 string isSetCheckCode = ""; 720 721 /// Code for the case statements storing the fields to the result struct. 722 string readMembersCode = ""; 723 724 // The last automatically assigned id – fields with no meta information 725 // are assigned (in lexical order) descending negative ids, starting with 726 // -1, just like the Thrift compiler does. 727 short lastId; 728 729 foreach (name; FieldNames!T) { 730 enum req = memberReq!(T, name, fieldMetaData); 731 if (req == TReq.REQUIRED) { 732 // For required fields, generate local bool flags to keep track 733 // whether the field has been encountered. 734 immutable n = "isSet_" ~ name; 735 isSetFlagCode ~= "bool " ~ n ~ ";\n"; 736 isSetCheckCode ~= "enforce(" ~ n ~ ", new TProtocolException(" ~ 737 "`Required field '" ~ name ~ "' not found in serialized data`, " ~ 738 "TProtocolException.Type.INVALID_DATA));\n"; 739 } 740 741 enum meta = find!`a.name == b`(mergeFieldMeta!(T, fieldMetaData), name); 742 static if (meta.empty) { 743 --lastId; 744 version (TVerboseCodegen) { 745 code ~= "pragma(msg, `[thrift.codegen.base.readStruct] Warning: No " ~ 746 "meta information for field '" ~ name ~ "' in struct '" ~ 747 T.stringof ~ "'. Assigned id: " ~ to!string(lastId) ~ ".`);\n"; 748 } 749 readMembersCode ~= readFieldCode!(MemberType!(T, name))( 750 name, lastId, req); 751 } else static if (req != TReq.IGNORE) { 752 readMembersCode ~= readFieldCode!(MemberType!(T, name))( 753 name, meta.front.id, req); 754 } 755 } 756 757 code ~= isSetFlagCode; 758 code ~= "p.readStructBegin();\n"; 759 code ~= "while (true) {\n"; 760 code ~= "auto f = p.readFieldBegin();\n"; 761 code ~= "if (f.type == TType.STOP) break;\n"; 762 code ~= "switch(f.id) {\n"; 763 code ~= readMembersCode; 764 code ~= "default: skip(p, f.type);\n"; 765 code ~= "}\n"; 766 code ~= "p.readFieldEnd();\n"; 767 code ~= "}\n"; 768 code ~= "p.readStructEnd();\n"; 769 code ~= isSetCheckCode; 770 771 return code; 772 }()); 773 } 774 775 /** 776 * Serializes a struct to the target protocol. 777 * 778 * Using the Protocol template parameter, the concrete TProtocol to use can be 779 * be specified. If the pointerStruct parameter is set to true, the struct 780 * fields are expected to be pointers to the actual data. This is used 781 * internally (combined with TPargsStruct) and usually should not be used in 782 * user code. 783 * 784 * This is a free function to make it possible to read exisiting structs from 785 * the wire without altering their definitions. 786 */ 787 void writeStruct(T, Protocol, alias fieldMetaData = cast(TFieldMeta[])null, 788 bool pointerStruct = false) (const T s, Protocol p) if (isTProtocol!Protocol) 789 { 790 mixin({ 791 // Check that all fields for which there is meta info are actually in the 792 // passed struct type. 793 string code = ""; 794 foreach (field; mergeFieldMeta!(T, fieldMetaData)) { 795 code ~= "static assert(is(MemberType!(T, `" ~ field.name ~ "`)));\n"; 796 } 797 798 // Check that required nullable members are non-null. 799 // WORKAROUND: To stop LDC from emitting the manifest constant »meta« below 800 // into the writeStruct function body this is inside the string mixin 801 // block – the code wouldn't depend on it (this is an LDC bug, and because 802 // of it a new array would be allocated on each method invocation at runtime). 803 foreach (name; StaticFilter!( 804 Compose!(isNullable, PApply!(MemberType, T)), 805 FieldNames!T 806 )) { 807 static if (memberReq!(T, name, fieldMetaData) == TReq.REQUIRED) { 808 code ~= "enforce(__traits(getMember, s, `" ~ name ~ "`) !is null, 809 new TException(`Required field '" ~ name ~ "' is null.`));\n"; 810 } 811 } 812 813 return code; 814 }()); 815 816 p.writeStructBegin(TStruct(T.stringof)); 817 mixin({ 818 string writeValueCode(ValueType)(string v, size_t level = 0) { 819 // Some non-ambigous names to use (shadowing is not allowed in D). 820 immutable elem = "elem" ~ to!string(level); 821 immutable key = "key" ~ to!string(level); 822 immutable value = "value" ~ to!string(level); 823 824 alias FullyUnqual!ValueType F; 825 static if (is(F == bool)) { 826 return "p.writeBool(" ~ v ~ ");"; 827 } else static if (is(F == byte)) { 828 return "p.writeByte(" ~ v ~ ");"; 829 } else static if (is(F == double)) { 830 return "p.writeDouble(" ~ v ~ ");"; 831 } else static if (is(F == short)) { 832 return "p.writeI16(" ~ v ~ ");"; 833 } else static if (is(F == int)) { 834 return "p.writeI32(" ~ v ~ ");"; 835 } else static if (is(F == long)) { 836 return "p.writeI64(" ~ v ~ ");"; 837 } else static if (is(F : string)) { 838 return "p.writeString(" ~ v ~ ");"; 839 } else static if (is(F == enum)) { 840 return "p.writeI32(cast(int)" ~ v ~ ");"; 841 } else static if (is(F _ : E[], E)) { 842 return "p.writeListBegin(TList(" ~ dToTTypeString!E ~ ", " ~ v ~ 843 ".length));\n" ~ 844 "foreach (" ~ elem ~ "; " ~ v ~ ") {\n" ~ 845 writeValueCode!E(elem, level + 1) ~ "\n" ~ 846 "}\n" ~ 847 "p.writeListEnd();"; 848 } else static if (is(F _ : V[K], K, V)) { 849 return "p.writeMapBegin(TMap(" ~ dToTTypeString!K ~ ", " ~ 850 dToTTypeString!V ~ ", " ~ v ~ ".length));\n" ~ 851 "foreach (" ~ key ~ ", " ~ value ~ "; " ~ v ~ ") {\n" ~ 852 writeValueCode!K(key, level + 1) ~ "\n" ~ 853 writeValueCode!V(value, level + 1) ~ "\n" ~ 854 "}\n" ~ 855 "p.writeMapEnd();"; 856 } else static if (is(F _ : HashSet!E, E)) { 857 return "p.writeSetBegin(TSet(" ~ dToTTypeString!E ~ ", " ~ v ~ 858 ".length));\n" ~ 859 "foreach (" ~ elem ~ "; " ~ v ~ "[]) {\n" ~ 860 writeValueCode!E(elem, level + 1) ~ "\n" ~ 861 "}\n" ~ 862 "p.writeSetEnd();"; 863 } else static if (is(F == struct) || is(F : TException)) { 864 static if (__traits(compiles, F.init.write(TProtocol.init))) { 865 return v ~ ".write(p);"; 866 } else { 867 return "writeStruct(" ~ v ~ ", p);"; 868 } 869 } else { 870 static assert(false, "Cannot represent type in Thrift: " ~ F.stringof); 871 } 872 } 873 874 string writeFieldCode(FieldType)(string name, short id, TReq req) { 875 string code; 876 if (!pointerStruct && req == TReq.OPTIONAL) { 877 code ~= "if (s.isSet!`" ~ name ~ "`) {\n"; 878 } 879 880 static if (pointerStruct && isPointer!FieldType) { 881 immutable v = "(*s." ~ name ~ ")"; 882 alias PointerTarget!FieldType F; 883 } else { 884 immutable v = "s." ~ name; 885 alias FieldType F; 886 } 887 888 code ~= "p.writeFieldBegin(TField(`" ~ name ~ "`, " ~ dToTTypeString!F ~ 889 ", " ~ to!string(id) ~ "));\n"; 890 code ~= writeValueCode!F(v) ~ "\n"; 891 code ~= "p.writeFieldEnd();\n"; 892 893 if (!pointerStruct && req == TReq.OPTIONAL) { 894 code ~= "}\n"; 895 } 896 return code; 897 } 898 899 // The last automatically assigned id – fields with no meta information 900 // are assigned (in lexical order) descending negative ids, starting with 901 // -1, just like the Thrift compiler does. 902 short lastId; 903 904 string code = ""; 905 foreach (name; FieldNames!T) { 906 alias MemberType!(T, name) F; 907 enum req = memberReq!(T, name, fieldMetaData); 908 enum meta = find!`a.name == b`(mergeFieldMeta!(T, fieldMetaData), name); 909 if (meta.empty) { 910 --lastId; 911 version (TVerboseCodegen) { 912 code ~= "pragma(msg, `[thrift.codegen.base.writeStruct] Warning: No " ~ 913 "meta information for field '" ~ name ~ "' in struct '" ~ 914 T.stringof ~ "'. Assigned id: " ~ to!string(lastId) ~ ".`);\n"; 915 } 916 code ~= writeFieldCode!F(name, lastId, req); 917 } else if (req != TReq.IGNORE) { 918 code ~= writeFieldCode!F(name, meta.front.id, req); 919 } 920 } 921 922 return code; 923 }()); 924 p.writeFieldStop(); 925 p.writeStructEnd(); 926 } 927 928 unittest { 929 // Ensure that the generated code at least compiles for the basic field type 930 // combinations. Functionality checks are covered by the rest of the test 931 // suite. 932 933 static struct Test { 934 // Non-nullable. 935 int a1; 936 int a2; 937 int a3; 938 int a4; 939 940 // Nullable. 941 string b1; 942 string b2; 943 string b3; 944 string b4; 945 946 mixin TStructHelpers!([ 947 TFieldMeta("a1", 1, TReq.OPT_IN_REQ_OUT), 948 TFieldMeta("a2", 2, TReq.OPTIONAL), 949 TFieldMeta("a3", 3, TReq.REQUIRED), 950 TFieldMeta("a4", 4, TReq.IGNORE), 951 TFieldMeta("b1", 5, TReq.OPT_IN_REQ_OUT), 952 TFieldMeta("b2", 6, TReq.OPTIONAL), 953 TFieldMeta("b3", 7, TReq.REQUIRED), 954 TFieldMeta("b4", 8, TReq.IGNORE), 955 ]); 956 } 957 958 static assert(__traits(compiles, { Test t; t.read(cast(TProtocol)null); })); 959 static assert(__traits(compiles, { Test t; t.write(cast(TProtocol)null); })); 960 } 961 962 // Ensure opEquals and toHash consistency. 963 unittest { 964 struct TestEquals { 965 int a1; 966 967 mixin TStructHelpers!([ 968 TFieldMeta("a1", 1, TReq.OPT_IN_REQ_OUT), 969 ]); 970 } 971 972 TestEquals a, b; 973 assert(a == b); 974 assert(a.toHash() == b.toHash()); 975 976 a.a1 = 42; 977 assert(a != b); 978 assert(a.toHash() != b.toHash()); 979 980 b.a1 = 42; 981 assert(a == b); 982 assert(a.toHash() == b.toHash()); 983 } 984 985 private { 986 /* 987 * Returns a D code string containing the matching TType value for a passed 988 * D type, e.g. dToTTypeString!byte == "TType.BYTE". 989 */ 990 template dToTTypeString(T) { 991 static if (is(FullyUnqual!T == bool)) { 992 enum dToTTypeString = "TType.BOOL"; 993 } else static if (is(FullyUnqual!T == byte)) { 994 enum dToTTypeString = "TType.BYTE"; 995 } else static if (is(FullyUnqual!T == double)) { 996 enum dToTTypeString = "TType.DOUBLE"; 997 } else static if (is(FullyUnqual!T == short)) { 998 enum dToTTypeString = "TType.I16"; 999 } else static if (is(FullyUnqual!T == int)) { 1000 enum dToTTypeString = "TType.I32"; 1001 } else static if (is(FullyUnqual!T == long)) { 1002 enum dToTTypeString = "TType.I64"; 1003 } else static if (is(FullyUnqual!T : string)) { 1004 enum dToTTypeString = "TType.STRING"; 1005 } else static if (is(FullyUnqual!T == enum)) { 1006 enum dToTTypeString = "TType.I32"; 1007 } else static if (is(FullyUnqual!T _ : U[], U)) { 1008 enum dToTTypeString = "TType.LIST"; 1009 } else static if (is(FullyUnqual!T _ : V[K], K, V)) { 1010 enum dToTTypeString = "TType.MAP"; 1011 } else static if (is(FullyUnqual!T _ : HashSet!E, E)) { 1012 enum dToTTypeString = "TType.SET"; 1013 } else static if (is(FullyUnqual!T == struct)) { 1014 enum dToTTypeString = "TType.STRUCT"; 1015 } else static if (is(FullyUnqual!T : TException)) { 1016 enum dToTTypeString = "TType.STRUCT"; 1017 } else { 1018 static assert(false, "Cannot represent type in Thrift: " ~ T.stringof); 1019 } 1020 } 1021 }