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 }