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 */
isTProtocolPair(T)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 */
isTProtocolOrPair(T)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 */
isService(T)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 */
isBaseService(T)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 */
isDerivedService(T)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)) {
toString()411 override string toString() const {
412 return thriftToStringImpl();
413 }
414
opEquals(Object other)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
toHash()424 override size_t toHash() const {
425 return thriftToHashImpl();
426 }
427 } else {
toString()428 string toString() const {
429 return thriftToStringImpl();
430 }
431
opEquals(ref const This other)432 bool opEquals(ref const This other) const {
433 return thriftOpEqualsImpl(other);
434 }
435
toHash()436 size_t toHash() const @safe nothrow {
437 return thriftToHashImpl();
438 }
439 }
440
thriftToStringImpl()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
thriftOpEqualsImpl(const ref This rhs)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
thriftToHashImpl()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)) {
this()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.
opCall()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).
thriftFieldInitCode(alias fieldMeta)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 */
TIsSetFlags(T,alias fieldMetaData)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 */
dToTTypeString(T)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 }
1022