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