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 module thrift.codegen.processor;
20 
21 import std.algorithm : find;
22 import std.array : empty, front;
23 import std.conv : to;
24 import std.traits : ParameterTypeTuple, ReturnType, Unqual;
25 import std.typetuple : allSatisfy, TypeTuple;
26 import std.variant : Variant;
27 import thrift.base;
28 import thrift.codegen.base;
29 import thrift.internal.codegen;
30 import thrift.internal.ctfe;
31 import thrift.protocol.base;
32 import thrift.protocol.processor;
33 
34 /**
35  * Service processor for Interface, which implements TProcessor by
36  * synchronously forwarding requests for the service methods to a handler
37  * implementing Interface.
38  *
39  * The generated class implements TProcessor and additionally allows a
40  * TProcessorEventHandler to be specified via the public eventHandler property.
41  * The constructor takes a single argument of type Interface, which is the
42  * handler to forward the requests to:
43  * ---
44  * this(Interface iface);
45  * TProcessorEventHandler eventHandler;
46  * ---
47  *
48  * If Interface is derived from another service BaseInterface, this class is
49  * also derived from TServiceProcessor!BaseInterface.
50  *
51  * The optional Protocols template tuple parameter can be used to specify
52  * one or more TProtocol implementations to specifically generate code for. If
53  * the actual types of the protocols passed to process() at runtime match one
54  * of the items from the list, the optimized code paths are taken, otherwise,
55  * a generic TProtocol version is used as fallback. For cases where the input
56  * and output protocols differ, TProtocolPair!(InputProtocol, OutputProtocol)
57  * can be used in the Protocols list:
58  * ---
59  * interface FooService { void foo(); }
60  * class FooImpl { override void foo {} }
61  *
62  * // Provides fast path if TBinaryProtocol!TBufferedTransport is used for
63  * // both input and output:
64  * alias TServiceProcessor!(FooService, TBinaryProtocol!TBufferedTransport)
65  *   BinaryProcessor;
66  *
67  * auto proc = new BinaryProcessor(new FooImpl());
68  *
69  * // Low overhead.
70  * proc.process(tBinaryProtocol(tBufferTransport(someSocket)));
71  *
72  * // Not in the specialization list – higher overhead.
73  * proc.process(tBinaryProtocol(tFramedTransport(someSocket)));
74  *
75  * // Same as above, but optimized for the Compact protocol backed by a
76  * // TPipedTransport for input and a TBufferedTransport for output.
77  * alias TServiceProcessor!(FooService, TProtocolPair!(
78  *   TCompactProtocol!TPipedTransport, TCompactProtocol!TBufferedTransport)
79  * ) MixedProcessor;
80  * ---
81  */
82 template TServiceProcessor(Interface, Protocols...) if (
83   isService!Interface && allSatisfy!(isTProtocolOrPair, Protocols)
84 ) {
85   mixin({
86     static if (is(Interface BaseInterfaces == super) && BaseInterfaces.length > 0) {
87       static assert(BaseInterfaces.length == 1,
88         "Services cannot be derived from more than one parent.");
89 
90       string code = "class TServiceProcessor : " ~
91         "TServiceProcessor!(BaseService!Interface) {\n";
92       code ~= "private Interface iface_;\n";
93 
94       string constructorCode = "this(Interface iface) {\n";
95       constructorCode ~= "super(iface);\n";
96       constructorCode ~= "iface_ = iface;\n";
97     } else {
98       string code = "class TServiceProcessor : TProcessor {";
99       code ~= q{
100         override bool process(TProtocol iprot, TProtocol oprot,
101           Variant context = Variant()
102         ) {
103           auto msg = iprot.readMessageBegin();
104 
105           void writeException(TApplicationException e) {
106             oprot.writeMessageBegin(TMessage(msg.name, TMessageType.EXCEPTION,
107               msg.seqid));
108             e.write(oprot);
109             oprot.writeMessageEnd();
110             oprot.transport.writeEnd();
111             oprot.transport.flush();
112           }
113 
114           if (msg.type != TMessageType.CALL && msg.type != TMessageType.ONEWAY) {
115             skip(iprot, TType.STRUCT);
116             iprot.readMessageEnd();
117             iprot.transport.readEnd();
118 
119             writeException(new TApplicationException(
120               TApplicationException.Type.INVALID_MESSAGE_TYPE));
121             return false;
122           }
123 
124           auto dg = msg.name in processMap_;
125           if (!dg) {
126             skip(iprot, TType.STRUCT);
127             iprot.readMessageEnd();
128             iprot.transport.readEnd();
129 
130             writeException(new TApplicationException("Invalid method name: '" ~
131               msg.name ~ "'.", TApplicationException.Type.INVALID_MESSAGE_TYPE));
132 
133             return false;
134           }
135 
136           (*dg)(msg.seqid, iprot, oprot, context);
137           return true;
138         }
139 
140         TProcessorEventHandler eventHandler;
141 
142         alias void delegate(int, TProtocol, TProtocol, Variant) ProcessFunc;
143         protected ProcessFunc[string] processMap_;
144         private Interface iface_;
145       };
146 
147       string constructorCode = "this(Interface iface) {\n";
148       constructorCode ~= "iface_ = iface;\n";
149     }
150 
151     // Generate the handling code for each method, consisting of the dispatch
152     // function, registering it in the constructor, and the actual templated
153     // handler function.
154     foreach (methodName;
155       FilterMethodNames!(Interface, __traits(derivedMembers, Interface))
156     ) {
157       // Register the processing function in the constructor.
158       immutable procFuncName = "process_" ~ methodName;
159       immutable dispatchFuncName = procFuncName ~ "_protocolDispatch";
160       constructorCode ~= "processMap_[`" ~ methodName ~ "`] = &" ~
161         dispatchFuncName ~ ";\n";
162 
163       bool methodMetaFound;
164       TMethodMeta methodMeta;
165       static if (is(typeof(Interface.methodMeta) : TMethodMeta[])) {
166         enum meta = find!`a.name == b`(Interface.methodMeta, methodName);
167         if (!meta.empty) {
168           methodMetaFound = true;
169           methodMeta = meta.front;
170         }
171       }
172 
173       // The dispatch function to call the specialized handler functions. We
174       // test the protocols if they can be converted to one of the passed
175       // protocol types, and if not, fall back to the generic TProtocol
176       // version of the processing function.
177       code ~= "void " ~ dispatchFuncName ~
178         "(int seqid, TProtocol iprot, TProtocol oprot, Variant context) {\n";
179       code ~= "foreach (Protocol; TypeTuple!(Protocols, TProtocol)) {\n";
180       code ~= q{
181         static if (is(Protocol _ : TProtocolPair!(I, O), I, O)) {
182           alias I IProt;
183           alias O OProt;
184         } else {
185           alias Protocol IProt;
186           alias Protocol OProt;
187         }
188         auto castedIProt = cast(IProt)iprot;
189         auto castedOProt = cast(OProt)oprot;
190       };
191       code ~= "if (castedIProt && castedOProt) {\n";
192       code ~= procFuncName ~
193         "!(IProt, OProt)(seqid, castedIProt, castedOProt, context);\n";
194       code ~= "return;\n";
195       code ~= "}\n";
196       code ~= "}\n";
197       code ~= "throw new TException(`Internal error: Null iprot/oprot " ~
198         "passed to processor protocol dispatch function.`);\n";
199       code ~= "}\n";
200 
201       // The actual handler function, templated on the input and output
202       // protocol types.
203       code ~= "void " ~ procFuncName ~ "(IProt, OProt)(int seqid, IProt " ~
204         "iprot, OProt oprot, Variant connectionContext) " ~
205         "if (isTProtocol!IProt && isTProtocol!OProt) {\n";
206       code ~= "TArgsStruct!(Interface, `" ~ methodName ~ "`) args;\n";
207 
208       // Store the (qualified) method name in a manifest constant to avoid
209       // having to litter the code below with lots of string manipulation.
210       code ~= "enum methodName = `" ~ methodName ~ "`;\n";
211 
212       code ~= q{
213         enum qName = Interface.stringof ~ "." ~ methodName;
214 
215         Variant callContext;
216         if (eventHandler) {
217           callContext = eventHandler.createContext(qName, connectionContext);
218         }
219 
220         scope (exit) {
221           if (eventHandler) {
222             eventHandler.deleteContext(callContext, qName);
223           }
224         }
225 
226         if (eventHandler) eventHandler.preRead(callContext, qName);
227 
228         args.read(iprot);
229         iprot.readMessageEnd();
230         iprot.transport.readEnd();
231 
232         if (eventHandler) eventHandler.postRead(callContext, qName);
233       };
234 
235       code ~= "TResultStruct!(Interface, `" ~ methodName ~ "`) result;\n";
236       code ~= "try {\n";
237 
238       // Generate the parameter list to pass to the called iface function.
239       string[] paramList;
240       foreach (i, _; ParameterTypeTuple!(mixin("Interface." ~ methodName))) {
241         string paramName;
242         if (methodMetaFound && i < methodMeta.params.length) {
243           paramName = methodMeta.params[i].name;
244         } else {
245           paramName = "param" ~ to!string(i + 1);
246         }
247         paramList ~= "args." ~ paramName;
248       }
249 
250       immutable call = "iface_." ~ methodName ~ "(" ~ ctfeJoin(paramList) ~ ")";
251       if (is(ReturnType!(mixin("Interface." ~ methodName)) == void)) {
252         code ~= call ~ ";\n";
253       } else {
254         code ~= "result.set!`success`(" ~ call ~ ");\n";
255       }
256 
257       // If this is not a oneway method, generate the receiving code.
258       if (!methodMetaFound || methodMeta.type != TMethodType.ONEWAY) {
259         if (methodMetaFound) {
260           foreach (e; methodMeta.exceptions) {
261             code ~= "} catch (Interface." ~ e.type ~ " " ~ e.name ~ ") {\n";
262             code ~= "result.set!`" ~ e.name ~ "`(" ~ e.name ~ ");\n";
263           }
264         }
265         code ~= "}\n";
266 
267         code ~= q{
268           catch (Exception e) {
269             if (eventHandler) {
270               eventHandler.handlerError(callContext, qName, e);
271             }
272 
273             auto x = new TApplicationException(to!string(e));
274             oprot.writeMessageBegin(
275               TMessage(methodName, TMessageType.EXCEPTION, seqid));
276             x.write(oprot);
277             oprot.writeMessageEnd();
278             oprot.transport.writeEnd();
279             oprot.transport.flush();
280             return;
281           }
282 
283           if (eventHandler) eventHandler.preWrite(callContext, qName);
284 
285           oprot.writeMessageBegin(TMessage(methodName,
286             TMessageType.REPLY, seqid));
287           result.write(oprot);
288           oprot.writeMessageEnd();
289           oprot.transport.writeEnd();
290           oprot.transport.flush();
291 
292           if (eventHandler) eventHandler.postWrite(callContext, qName);
293         };
294       } else {
295         // For oneway methods, we obviously cannot notify the client of any
296         // exceptions, just call the event handler if one is set.
297         code ~= "}\n";
298         code ~= q{
299           catch (Exception e) {
300             if (eventHandler) {
301               eventHandler.handlerError(callContext, qName, e);
302             }
303             return;
304           }
305 
306           if (eventHandler) eventHandler.onewayComplete(callContext, qName);
307         };
308       }
309       code ~= "}\n";
310 
311     }
312 
313     code ~= constructorCode ~ "}\n";
314     code ~= "}\n";
315 
316     return code;
317   }());
318 }
319 
320 /**
321  * A struct representing the arguments of a Thrift method call.
322  *
323  * There should usually be no reason to use this directly without the help of
324  * TServiceProcessor, but it is documented publicly to help debugging in case
325  * of CTFE errors.
326  *
327  * Consider this example:
328  * ---
329  * interface Foo {
330  *   int bar(string a, bool b);
331  *
332  *   enum methodMeta = [
333  *     TMethodMeta("bar", [TParamMeta("a", 1), TParamMeta("b", 2)])
334  *   ];
335  * }
336  *
337  * alias TArgsStruct!(Foo, "bar") FooBarArgs;
338  * ---
339  *
340  * The definition of FooBarArgs is equivalent to:
341  * ---
342  * struct FooBarArgs {
343  *   string a;
344  *   bool b;
345  *
346  *   mixin TStructHelpers!([TFieldMeta("a", 1, TReq.OPT_IN_REQ_OUT),
347  *     TFieldMeta("b", 2, TReq.OPT_IN_REQ_OUT)]);
348  * }
349  * ---
350  *
351  * If the TVerboseCodegen version is defined, a warning message is issued at
352  * compilation if no TMethodMeta for Interface.methodName is found.
353  */
354 template TArgsStruct(Interface, string methodName) {
355   static assert(is(typeof(mixin("Interface." ~ methodName))),
356     "Could not find method '" ~ methodName ~ "' in '" ~ Interface.stringof ~ "'.");
357   mixin({
358     bool methodMetaFound;
359     TMethodMeta methodMeta;
360     static if (is(typeof(Interface.methodMeta) : TMethodMeta[])) {
361       auto meta = find!`a.name == b`(Interface.methodMeta, methodName);
362       if (!meta.empty) {
363         methodMetaFound = true;
364         methodMeta = meta.front;
365       }
366     }
367 
368     string memberCode;
369     string[] fieldMetaCodes;
370     foreach (i, _; ParameterTypeTuple!(mixin("Interface." ~ methodName))) {
371       // If we have no meta information, just use param1, param2, etc. as
372       // field names, it shouldn't really matter anyway. 1-based »indexing«
373       // is used to match the common scheme in the Thrift world.
374       string memberId;
375       string memberName;
376       if (methodMetaFound && i < methodMeta.params.length) {
377         memberId = to!string(methodMeta.params[i].id);
378         memberName = methodMeta.params[i].name;
379       } else {
380         memberId = to!string(i + 1);
381         memberName = "param" ~ to!string(i + 1);
382       }
383 
384       // Unqual!() is needed to generate mutable fields for ref const()
385       // struct parameters.
386       memberCode ~= "Unqual!(ParameterTypeTuple!(Interface." ~ methodName ~
387         ")[" ~ to!string(i) ~ "])" ~ memberName ~ ";\n";
388 
389       fieldMetaCodes ~= "TFieldMeta(`" ~ memberName ~ "`, " ~ memberId ~
390         ", TReq.OPT_IN_REQ_OUT)";
391     }
392 
393     string code = "struct TArgsStruct {\n";
394     code ~= memberCode;
395     version (TVerboseCodegen) {
396       if (!methodMetaFound &&
397         ParameterTypeTuple!(mixin("Interface." ~ methodName)).length > 0)
398       {
399         code ~= "pragma(msg, `[thrift.codegen.processor.TArgsStruct] Warning: No " ~
400           "meta information for method '" ~ methodName ~ "' in service '" ~
401           Interface.stringof ~ "' found.`);\n";
402       }
403     }
404     immutable fieldMetaCode =
405       fieldMetaCodes.empty ? "" : "[" ~ ctfeJoin(fieldMetaCodes) ~ "]";
406     code ~= "mixin TStructHelpers!(" ~ fieldMetaCode  ~ ");\n";
407     code ~= "}\n";
408     return code;
409   }());
410 }
411 
412 /**
413  * A struct representing the result of a Thrift method call.
414  *
415  * It contains a field called "success" for the return value of the function
416  * (with id 0), and additional fields for the exceptions declared for the
417  * method, if any.
418  *
419  * There should usually be no reason to use this directly without the help of
420  * TServiceProcessor, but it is documented publicly to help debugging in case
421  * of CTFE errors.
422  *
423  * Consider the following example:
424  * ---
425  * interface Foo {
426  *   int bar(string a);
427  *
428  *   alias .FooException FooException;
429  *
430  *   enum methodMeta = [
431  *     TMethodMeta("bar",
432  *       [TParamMeta("a", 1)],
433  *       [TExceptionMeta("fooe", 1, "FooException")]
434  *     )
435  *   ];
436  * }
437  * alias TResultStruct!(Foo, "bar") FooBarResult;
438  * ---
439  *
440  * The definition of FooBarResult is equivalent to:
441  * ---
442  * struct FooBarResult {
443  *   int success;
444  *   FooException fooe;
445  *
446  *   mixin(TStructHelpers!([TFieldMeta("success", 0, TReq.OPTIONAL),
447  *     TFieldMeta("fooe", 1, TReq.OPTIONAL)]));
448  * }
449  * ---
450  *
451  * If the TVerboseCodegen version is defined, a warning message is issued at
452  * compilation if no TMethodMeta for Interface.methodName is found.
453  */
454 template TResultStruct(Interface, string methodName) {
455   static assert(is(typeof(mixin("Interface." ~ methodName))),
456     "Could not find method '" ~ methodName ~ "' in '" ~ Interface.stringof ~ "'.");
457 
458   mixin({
459     string code = "struct TResultStruct {\n";
460 
461     string[] fieldMetaCodes;
462 
463     static if (!is(ReturnType!(mixin("Interface." ~ methodName)) == void)) {
464       code ~= "ReturnType!(Interface." ~ methodName ~ ") success;\n";
465       fieldMetaCodes ~= "TFieldMeta(`success`, 0, TReq.OPTIONAL)";
466     }
467 
468     bool methodMetaFound;
469     static if (is(typeof(Interface.methodMeta) : TMethodMeta[])) {
470       auto meta = find!`a.name == b`(Interface.methodMeta, methodName);
471       if (!meta.empty) {
472         foreach (e; meta.front.exceptions) {
473           code ~= "Interface." ~ e.type ~ " " ~ e.name ~ ";\n";
474           fieldMetaCodes ~= "TFieldMeta(`" ~ e.name ~ "`, " ~ to!string(e.id) ~
475             ", TReq.OPTIONAL)";
476         }
477         methodMetaFound = true;
478       }
479     }
480 
481     version (TVerboseCodegen) {
482       if (!methodMetaFound &&
483         ParameterTypeTuple!(mixin("Interface." ~ methodName)).length > 0)
484       {
485         code ~= "pragma(msg, `[thrift.codegen.processor.TResultStruct] Warning: No " ~
486           "meta information for method '" ~ methodName ~ "' in service '" ~
487           Interface.stringof ~ "' found.`);\n";
488       }
489     }
490 
491     immutable fieldMetaCode =
492       fieldMetaCodes.empty ? "" : "[" ~ ctfeJoin(fieldMetaCodes) ~ "]";
493     code ~= "mixin TStructHelpers!(" ~ fieldMetaCode  ~ ");\n";
494     code ~= "}\n";
495     return code;
496   }());
497 }