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