blob: a9150d75e76233680efe5321317110e2c1c0aed5 [file] [log] [blame]
Jake Farrellb95b0ff2012-03-22 21:49:10 +00001/*
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.
40module thrift.codegen.base;
41
42import std.algorithm : find;
43import std.array : empty, front;
44import std.conv : to;
45import std.exception : enforce;
46import std.traits : BaseTypeTuple, isPointer, isSomeFunction, pointerTarget,
47 ReturnType;
48import thrift.base;
49import thrift.internal.codegen;
50import 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 */
61enum 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 */
90enum 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 */
103struct 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 */
122struct 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 */
141struct 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 */
157struct 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 */
174struct TProtocolPair(InputProtocol, OutputProtocol) if (
175 isTProtocol!InputProtocol && isTProtocol!OutputProtocol
176) {}
177
178/**
179 * true if T is a TProtocolPair.
180 */
181template 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
189unittest {
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 */
197template isTProtocolOrPair(T) {
198 enum isTProtocolOrPair = isTProtocol!T || isTProtocolPair!T;
199}
200
201unittest {
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 */
210template 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 */
217template 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 */
230template 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 */
243template 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 */
358mixin template TStructHelpers(alias fieldMetaData = cast(TFieldMeta[])null) if (
359 is(typeof(fieldMetaData) : TFieldMeta[])
360) {
Roger Meier62320142014-01-12 20:00:31 +0100361 import std.algorithm : any;
Jake Farrellb95b0ff2012-03-22 21:49:10 +0000362 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
Roger Meierb70e04c2012-04-28 19:20:23 +0000371 static if (TIsSetFlags!(This, fieldMetaData).tupleof.length > 0) {
Jake Farrellb95b0ff2012-03-22 21:49:10 +0000372 // 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
Roger Meier62320142014-01-12 20:00:31 +0100462 static if (any!`!a.defaultValue.empty`(mergeFieldMeta!(This, fieldMetaData))) {
Jake Farrellb95b0ff2012-03-22 21:49:10 +0000463 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).
494string 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
Roger Meier62320142014-01-12 20:00:31 +0100503unittest {
Jake Farrellb95b0ff2012-03-22 21:49:10 +0000504 // Cannot make this nested in the unittest block due to a »no size yet for
505 // forward reference« error.
Roger Meier62320142014-01-12 20:00:31 +0100506 static struct Foo {
Jake Farrellb95b0ff2012-03-22 21:49:10 +0000507 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 }
Roger Meier62320142014-01-12 20:00:31 +0100517
Jake Farrellb95b0ff2012-03-22 21:49:10 +0000518 auto f = Foo();
519
520 f.set!"b"(12345);
521 assert(f.isSet!"b");
522 f.unset!"b"();
523 assert(!f.isSet!"b");
524 f.set!"b"(12345);
525 assert(f.isSet!"b");
526 f.unset!"b"();
527
528 f.a = "a string";
529 assert(f.toString() == `Foo(a: a string, b: 0 (unset), c: 4)`);
530}
531
Roger Meierb70e04c2012-04-28 19:20:23 +0000532
Jake Farrellb95b0ff2012-03-22 21:49:10 +0000533/**
534 * Generates an eponymous struct with boolean flags for the non-required
Roger Meierb70e04c2012-04-28 19:20:23 +0000535 * non-nullable fields of T.
Jake Farrellb95b0ff2012-03-22 21:49:10 +0000536 *
Roger Meierb70e04c2012-04-28 19:20:23 +0000537 * Nullable fields are just set to null to signal »not set«, so no flag is
538 * emitted for them, even if they are optional.
Jake Farrellb95b0ff2012-03-22 21:49:10 +0000539 *
540 * In most cases, you do not want to use this directly, but via TStructHelpers
541 * instead.
542 */
Jake Farrellb95b0ff2012-03-22 21:49:10 +0000543template TIsSetFlags(T, alias fieldMetaData) {
544 mixin({
Roger Meierb70e04c2012-04-28 19:20:23 +0000545 string code = "struct TIsSetFlags {\n";
546 foreach (meta; fieldMetaData) {
547 code ~= "static if (!is(MemberType!(T, `" ~ meta.name ~ "`))) {\n";
548 code ~= q{
549 static assert(false, "Field '" ~ meta.name ~
550 "' referenced in metadata not present in struct '" ~ T.stringof ~ "'.");
551 };
552 code ~= "}";
553 if (meta.req == TReq.OPTIONAL || meta.req == TReq.OPT_IN_REQ_OUT) {
554 code ~= "else static if (!isNullable!(MemberType!(T, `" ~ meta.name ~ "`))) {\n";
555 code ~= " bool " ~ meta.name ~ ";\n";
556 code ~= "}\n";
Jake Farrellb95b0ff2012-03-22 21:49:10 +0000557 }
558 }
Roger Meierb70e04c2012-04-28 19:20:23 +0000559 code ~= "}";
560 return code;
Jake Farrellb95b0ff2012-03-22 21:49:10 +0000561 }());
562}
563
564/**
565 * Deserializes a Thrift struct from a protocol.
566 *
567 * Using the Protocol template parameter, the concrete TProtocol to use can be
568 * be specified. If the pointerStruct parameter is set to true, the struct
569 * fields are expected to be pointers to the actual data. This is used
570 * internally (combined with TPResultStruct) and usually should not be used in
571 * user code.
572 *
573 * This is a free function to make it possible to read exisiting structs from
574 * the wire without altering their definitions.
575 */
576void readStruct(T, Protocol, alias fieldMetaData = cast(TFieldMeta[])null,
577 bool pointerStruct = false)(ref T s, Protocol p) if (isTProtocol!Protocol)
578{
579 mixin({
580 string code;
581
582 // Check that all fields for which there is meta info are actually in the
583 // passed struct type.
584 foreach (field; mergeFieldMeta!(T, fieldMetaData)) {
585 code ~= "static assert(is(MemberType!(T, `" ~ field.name ~ "`)));\n";
586 }
587
588 // Returns the code string for reading a value of type F off the wire and
589 // assigning it to v. The level parameter is used to make sure that there
590 // are no conflicting variable names on recursive calls.
591 string readValueCode(ValueType)(string v, size_t level = 0) {
592 // Some non-ambigous names to use (shadowing is not allowed in D).
593 immutable i = "i" ~ to!string(level);
594 immutable elem = "elem" ~ to!string(level);
595 immutable key = "key" ~ to!string(level);
596 immutable list = "list" ~ to!string(level);
597 immutable map = "map" ~ to!string(level);
598 immutable set = "set" ~ to!string(level);
599 immutable value = "value" ~ to!string(level);
600
601 alias FullyUnqual!ValueType F;
602
603 static if (is(F == bool)) {
604 return v ~ " = p.readBool();";
605 } else static if (is(F == byte)) {
606 return v ~ " = p.readByte();";
607 } else static if (is(F == double)) {
608 return v ~ " = p.readDouble();";
609 } else static if (is(F == short)) {
610 return v ~ " = p.readI16();";
611 } else static if (is(F == int)) {
612 return v ~ " = p.readI32();";
613 } else static if (is(F == long)) {
614 return v ~ " = p.readI64();";
615 } else static if (is(F : string)) {
616 return v ~ " = p.readString();";
617 } else static if (is(F == enum)) {
618 return v ~ " = cast(typeof(" ~ v ~ "))p.readI32();";
619 } else static if (is(F _ : E[], E)) {
620 return "{\n" ~
621 "auto " ~ list ~ " = p.readListBegin();\n" ~
622 // TODO: Check element type here?
623 v ~ " = new typeof(" ~ v ~ "[0])[" ~ list ~ ".size];\n" ~
624 "foreach (" ~ i ~ "; 0 .. " ~ list ~ ".size) {\n" ~
625 readValueCode!E(v ~ "[" ~ i ~ "]", level + 1) ~ "\n" ~
626 "}\n" ~
627 "p.readListEnd();\n" ~
628 "}";
629 } else static if (is(F _ : V[K], K, V)) {
630 return "{\n" ~
631 "auto " ~ map ~ " = p.readMapBegin();" ~
632 v ~ " = null;\n" ~
633 // TODO: Check key/value types here?
634 "foreach (" ~ i ~ "; 0 .. " ~ map ~ ".size) {\n" ~
635 "FullyUnqual!(typeof(" ~ v ~ ".keys[0])) " ~ key ~ ";\n" ~
636 readValueCode!K(key, level + 1) ~ "\n" ~
637 "typeof(" ~ v ~ ".values[0]) " ~ value ~ ";\n" ~
638 readValueCode!V(value, level + 1) ~ "\n" ~
639 v ~ "[cast(typeof(" ~ v ~ ".keys[0]))" ~ key ~ "] = " ~ value ~ ";\n" ~
640 "}\n" ~
641 "p.readMapEnd();" ~
642 "}";
643 } else static if (is(F _ : HashSet!(E), E)) {
644 return "{\n" ~
645 "auto " ~ set ~ " = p.readSetBegin();" ~
646 // TODO: Check element type here?
647 v ~ " = new typeof(" ~ v ~ ")();\n" ~
648 "foreach (" ~ i ~ "; 0 .. " ~ set ~ ".size) {\n" ~
649 "typeof(" ~ v ~ "[][0]) " ~ elem ~ ";\n" ~
650 readValueCode!E(elem, level + 1) ~ "\n" ~
651 v ~ " ~= " ~ elem ~ ";\n" ~
652 "}\n" ~
653 "p.readSetEnd();" ~
654 "}";
655 } else static if (is(F == struct) || is(F : TException)) {
656 static if (is(F == struct)) {
657 auto result = v ~ " = typeof(" ~ v ~ ")();\n";
658 } else {
659 auto result = v ~ " = new typeof(" ~ v ~ ")();\n";
660 }
661
662 static if (__traits(compiles, F.init.read(TProtocol.init))) {
663 result ~= v ~ ".read(p);";
664 } else {
665 result ~= "readStruct(" ~ v ~ ", p);";
666 }
667 return result;
668 } else {
669 static assert(false, "Cannot represent type in Thrift: " ~ F.stringof);
670 }
671 }
672
673 string readFieldCode(FieldType)(string name, short id, TReq req) {
674 static if (pointerStruct && isPointer!FieldType) {
675 immutable v = "(*s." ~ name ~ ")";
676 alias pointerTarget!FieldType F;
677 } else {
678 immutable v = "s." ~ name;
679 alias FieldType F;
680 }
681
682 string code = "case " ~ to!string(id) ~ ":\n";
683 code ~= "if (f.type == " ~ dToTTypeString!F ~ ") {\n";
684 code ~= readValueCode!F(v) ~ "\n";
685 if (req == TReq.REQUIRED) {
686 // For required fields, set the corresponding local isSet variable.
687 code ~= "isSet_" ~ name ~ " = true;\n";
688 } else if (!isNullable!F){
689 code ~= "s.isSetFlags." ~ name ~ " = true;\n";
690 }
691 code ~= "} else skip(p, f.type);\n";
692 code ~= "break;\n";
693 return code;
694 }
695
696 // Code for the local boolean flags used to make sure required fields have
697 // been found.
698 string isSetFlagCode = "";
699
700 // Code for checking whether the flags for the required fields are true.
701 string isSetCheckCode = "";
702
703 /// Code for the case statements storing the fields to the result struct.
704 string readMembersCode = "";
705
706 // The last automatically assigned id – fields with no meta information
707 // are assigned (in lexical order) descending negative ids, starting with
708 // -1, just like the Thrift compiler does.
709 short lastId;
710
711 foreach (name; FieldNames!T) {
712 enum req = memberReq!(T, name, fieldMetaData);
713 if (req == TReq.REQUIRED) {
714 // For required fields, generate local bool flags to keep track
715 // whether the field has been encountered.
716 immutable n = "isSet_" ~ name;
717 isSetFlagCode ~= "bool " ~ n ~ ";\n";
718 isSetCheckCode ~= "enforce(" ~ n ~ ", new TProtocolException(" ~
719 "`Required field '" ~ name ~ "' not found in serialized data`, " ~
720 "TProtocolException.Type.INVALID_DATA));\n";
721 }
722
723 enum meta = find!`a.name == b`(mergeFieldMeta!(T, fieldMetaData), name);
724 static if (meta.empty) {
725 --lastId;
726 version (TVerboseCodegen) {
727 code ~= "pragma(msg, `[thrift.codegen.base.readStruct] Warning: No " ~
728 "meta information for field '" ~ name ~ "' in struct '" ~
729 T.stringof ~ "'. Assigned id: " ~ to!string(lastId) ~ ".`);\n";
730 }
731 readMembersCode ~= readFieldCode!(MemberType!(T, name))(
732 name, lastId, req);
733 } else static if (req != TReq.IGNORE) {
734 readMembersCode ~= readFieldCode!(MemberType!(T, name))(
735 name, meta.front.id, req);
736 }
737 }
738
739 code ~= isSetFlagCode;
740 code ~= "p.readStructBegin();\n";
741 code ~= "while (true) {\n";
742 code ~= "auto f = p.readFieldBegin();\n";
743 code ~= "if (f.type == TType.STOP) break;\n";
744 code ~= "switch(f.id) {\n";
745 code ~= readMembersCode;
746 code ~= "default: skip(p, f.type);\n";
747 code ~= "}\n";
748 code ~= "p.readFieldEnd();\n";
749 code ~= "}\n";
750 code ~= "p.readStructEnd();\n";
751 code ~= isSetCheckCode;
752
753 return code;
754 }());
755}
756
757/**
758 * Serializes a struct to the target protocol.
759 *
760 * Using the Protocol template parameter, the concrete TProtocol to use can be
761 * be specified. If the pointerStruct parameter is set to true, the struct
762 * fields are expected to be pointers to the actual data. This is used
763 * internally (combined with TPargsStruct) and usually should not be used in
764 * user code.
765 *
766 * This is a free function to make it possible to read exisiting structs from
767 * the wire without altering their definitions.
768 */
769void writeStruct(T, Protocol, alias fieldMetaData = cast(TFieldMeta[])null,
770 bool pointerStruct = false) (const T s, Protocol p) if (isTProtocol!Protocol)
771{
772 mixin({
773 // Check that all fields for which there is meta info are actually in the
774 // passed struct type.
775 string code = "";
776 foreach (field; mergeFieldMeta!(T, fieldMetaData)) {
777 code ~= "static assert(is(MemberType!(T, `" ~ field.name ~ "`)));\n";
778 }
779
780 // Check that required nullable members are non-null.
781 // WORKAROUND: To stop LDC from emitting the manifest constant »meta« below
782 // into the writeStruct function body this is inside the string mixin
783 // block – the code wouldn't depend on it (this is an LDC bug, and because
Roger Meier40633a62012-06-24 19:20:58 +0000784 // of it a new array would be allocated on each method invocation at runtime).
Jake Farrellb95b0ff2012-03-22 21:49:10 +0000785 foreach (name; StaticFilter!(
786 Compose!(isNullable, PApply!(MemberType, T)),
787 FieldNames!T
788 )) {
789 static if (memberReq!(T, name, fieldMetaData) == TReq.REQUIRED) {
Roger Meier40633a62012-06-24 19:20:58 +0000790 code ~= "enforce(__traits(getMember, s, `" ~ name ~ "`) !is null,
791 new TException(`Required field '" ~ name ~ "' is null.`));\n";
Jake Farrellb95b0ff2012-03-22 21:49:10 +0000792 }
793 }
794
795 return code;
796 }());
797
798 p.writeStructBegin(TStruct(T.stringof));
799 mixin({
800 string writeValueCode(ValueType)(string v, size_t level = 0) {
801 // Some non-ambigous names to use (shadowing is not allowed in D).
802 immutable elem = "elem" ~ to!string(level);
803 immutable key = "key" ~ to!string(level);
804 immutable value = "value" ~ to!string(level);
805
806 alias FullyUnqual!ValueType F;
807 static if (is(F == bool)) {
808 return "p.writeBool(" ~ v ~ ");";
809 } else static if (is(F == byte)) {
810 return "p.writeByte(" ~ v ~ ");";
811 } else static if (is(F == double)) {
812 return "p.writeDouble(" ~ v ~ ");";
813 } else static if (is(F == short)) {
814 return "p.writeI16(" ~ v ~ ");";
815 } else static if (is(F == int)) {
816 return "p.writeI32(" ~ v ~ ");";
817 } else static if (is(F == long)) {
818 return "p.writeI64(" ~ v ~ ");";
819 } else static if (is(F : string)) {
820 return "p.writeString(" ~ v ~ ");";
821 } else static if (is(F == enum)) {
822 return "p.writeI32(cast(int)" ~ v ~ ");";
823 } else static if (is(F _ : E[], E)) {
824 return "p.writeListBegin(TList(" ~ dToTTypeString!E ~ ", " ~ v ~
825 ".length));\n" ~
826 "foreach (" ~ elem ~ "; " ~ v ~ ") {\n" ~
827 writeValueCode!E(elem, level + 1) ~ "\n" ~
828 "}\n" ~
829 "p.writeListEnd();";
830 } else static if (is(F _ : V[K], K, V)) {
831 return "p.writeMapBegin(TMap(" ~ dToTTypeString!K ~ ", " ~
832 dToTTypeString!V ~ ", " ~ v ~ ".length));\n" ~
833 "foreach (" ~ key ~ ", " ~ value ~ "; " ~ v ~ ") {\n" ~
834 writeValueCode!K(key, level + 1) ~ "\n" ~
835 writeValueCode!V(value, level + 1) ~ "\n" ~
836 "}\n" ~
837 "p.writeMapEnd();";
838 } else static if (is(F _ : HashSet!E, E)) {
839 return "p.writeSetBegin(TSet(" ~ dToTTypeString!E ~ ", " ~ v ~
840 ".length));\n" ~
841 "foreach (" ~ elem ~ "; " ~ v ~ "[]) {\n" ~
842 writeValueCode!E(elem, level + 1) ~ "\n" ~
843 "}\n" ~
844 "p.writeSetEnd();";
845 } else static if (is(F == struct) || is(F : TException)) {
846 static if (__traits(compiles, F.init.write(TProtocol.init))) {
847 return v ~ ".write(p);";
848 } else {
849 return "writeStruct(" ~ v ~ ", p);";
850 }
851 } else {
852 static assert(false, "Cannot represent type in Thrift: " ~ F.stringof);
853 }
854 }
855
856 string writeFieldCode(FieldType)(string name, short id, TReq req) {
857 string code;
858 if (!pointerStruct && req == TReq.OPTIONAL) {
859 code ~= "if (s.isSet!`" ~ name ~ "`) {\n";
860 }
861
862 static if (pointerStruct && isPointer!FieldType) {
863 immutable v = "(*s." ~ name ~ ")";
864 alias pointerTarget!FieldType F;
865 } else {
866 immutable v = "s." ~ name;
867 alias FieldType F;
868 }
869
870 code ~= "p.writeFieldBegin(TField(`" ~ name ~ "`, " ~ dToTTypeString!F ~
871 ", " ~ to!string(id) ~ "));\n";
872 code ~= writeValueCode!F(v) ~ "\n";
873 code ~= "p.writeFieldEnd();\n";
874
875 if (!pointerStruct && req == TReq.OPTIONAL) {
876 code ~= "}\n";
877 }
878 return code;
879 }
880
881 // The last automatically assigned id – fields with no meta information
882 // are assigned (in lexical order) descending negative ids, starting with
883 // -1, just like the Thrift compiler does.
884 short lastId;
885
886 string code = "";
887 foreach (name; FieldNames!T) {
888 alias MemberType!(T, name) F;
889 enum req = memberReq!(T, name, fieldMetaData);
890 enum meta = find!`a.name == b`(mergeFieldMeta!(T, fieldMetaData), name);
891 if (meta.empty) {
892 --lastId;
893 version (TVerboseCodegen) {
894 code ~= "pragma(msg, `[thrift.codegen.base.writeStruct] Warning: No " ~
895 "meta information for field '" ~ name ~ "' in struct '" ~
896 T.stringof ~ "'. Assigned id: " ~ to!string(lastId) ~ ".`);\n";
897 }
898 code ~= writeFieldCode!F(name, lastId, req);
899 } else if (req != TReq.IGNORE) {
900 code ~= writeFieldCode!F(name, meta.front.id, req);
901 }
902 }
903
904 return code;
905 }());
906 p.writeFieldStop();
907 p.writeStructEnd();
908}
909
Roger Meier40633a62012-06-24 19:20:58 +0000910unittest {
911 // Ensure that the generated code at least compiles for the basic field type
912 // combinations. Functionality checks are covered by the rest of the test
913 // suite.
914
Roger Meiere3f67102013-01-05 20:46:43 +0100915 static struct Test {
Roger Meier40633a62012-06-24 19:20:58 +0000916 // Non-nullable.
917 int a1;
918 int a2;
919 int a3;
920 int a4;
921
922 // Nullable.
923 string b1;
924 string b2;
925 string b3;
926 string b4;
927
928 mixin TStructHelpers!([
929 TFieldMeta("a1", 1, TReq.OPT_IN_REQ_OUT),
930 TFieldMeta("a2", 2, TReq.OPTIONAL),
931 TFieldMeta("a3", 3, TReq.REQUIRED),
932 TFieldMeta("a4", 4, TReq.IGNORE),
933 TFieldMeta("b1", 5, TReq.OPT_IN_REQ_OUT),
934 TFieldMeta("b2", 6, TReq.OPTIONAL),
935 TFieldMeta("b3", 7, TReq.REQUIRED),
936 TFieldMeta("b4", 8, TReq.IGNORE),
937 ]);
938 }
939
940 static assert(__traits(compiles, { Test t; t.read(cast(TProtocol)null); }));
941 static assert(__traits(compiles, { Test t; t.write(cast(TProtocol)null); }));
942}
943
Jake Farrellb95b0ff2012-03-22 21:49:10 +0000944private {
945 /*
946 * Returns a D code string containing the matching TType value for a passed
947 * D type, e.g. dToTTypeString!byte == "TType.BYTE".
948 */
949 template dToTTypeString(T) {
950 static if (is(FullyUnqual!T == bool)) {
951 enum dToTTypeString = "TType.BOOL";
952 } else static if (is(FullyUnqual!T == byte)) {
953 enum dToTTypeString = "TType.BYTE";
954 } else static if (is(FullyUnqual!T == double)) {
955 enum dToTTypeString = "TType.DOUBLE";
956 } else static if (is(FullyUnqual!T == short)) {
957 enum dToTTypeString = "TType.I16";
958 } else static if (is(FullyUnqual!T == int)) {
959 enum dToTTypeString = "TType.I32";
960 } else static if (is(FullyUnqual!T == long)) {
961 enum dToTTypeString = "TType.I64";
962 } else static if (is(FullyUnqual!T : string)) {
963 enum dToTTypeString = "TType.STRING";
964 } else static if (is(FullyUnqual!T == enum)) {
965 enum dToTTypeString = "TType.I32";
966 } else static if (is(FullyUnqual!T _ : U[], U)) {
967 enum dToTTypeString = "TType.LIST";
968 } else static if (is(FullyUnqual!T _ : V[K], K, V)) {
969 enum dToTTypeString = "TType.MAP";
970 } else static if (is(FullyUnqual!T _ : HashSet!E, E)) {
971 enum dToTTypeString = "TType.SET";
972 } else static if (is(FullyUnqual!T == struct)) {
973 enum dToTTypeString = "TType.STRUCT";
974 } else static if (is(FullyUnqual!T : TException)) {
975 enum dToTTypeString = "TType.STRUCT";
976 } else {
977 static assert(false, "Cannot represent type in Thrift: " ~ T.stringof);
978 }
979 }
980}