blob: 9755ea8a38c0c5ef154072faab4f7e333fc93fa6 [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 */
19module thrift.codegen.client;
20
21import std.algorithm : find;
22import std.array : empty, front;
23import std.conv : to;
24import std.traits : isSomeFunction, ParameterStorageClass,
25 ParameterStorageClassTuple, ParameterTypeTuple, ReturnType;
26import thrift.codegen.base;
27import thrift.internal.codegen;
28import thrift.internal.ctfe;
29import thrift.protocol.base;
30
31/**
32 * Thrift service client, which implements an interface by synchronously
33 * calling a server over a TProtocol.
34 *
35 * TClientBase simply extends Interface with generic input/output protocol
36 * properties to serve as a supertype for all TClients for the same service,
37 * which might be instantiated with different concrete protocol types (there
38 * is no covariance for template type parameters). If Interface is derived
39 * from another interface BaseInterface, it also extends
40 * TClientBase!BaseInterface.
41 *
42 * TClient is the class that actually implements TClientBase. Just as
43 * TClientBase, it is also derived from TClient!BaseInterface for inheriting
44 * services.
45 *
46 * TClient takes two optional template arguments which can be used for
47 * specifying the actual TProtocol implementation used for optimization
48 * purposes, as virtual calls can completely be eliminated then. If
49 * OutputProtocol is not specified, it is assumed to be the same as
50 * InputProtocol. The protocol properties defined by TClientBase are exposed
51 * with their concrete type (return type covariance).
52 *
53 * In addition to implementing TClientBase!Interface, TClient offers the
54 * following constructors:
55 * ---
56 * this(InputProtocol iprot, OutputProtocol oprot);
57 * // Only if is(InputProtocol == OutputProtocol), to use the same protocol
58 * // for both input and output:
59 * this(InputProtocol prot);
60 * ---
61 *
62 * The sequence id of the method calls starts at zero and is automatically
63 * incremented.
64 */
65interface TClientBase(Interface) if (isBaseService!Interface) : Interface {
66 /**
67 * The input protocol used by the client.
68 */
69 TProtocol inputProtocol() @property;
70
71 /**
72 * The output protocol used by the client.
73 */
74 TProtocol outputProtocol() @property;
75}
76
77/// Ditto
78interface TClientBase(Interface) if (isDerivedService!Interface) :
79 TClientBase!(BaseService!Interface), Interface {}
80
81/// Ditto
82template TClient(Interface, InputProtocol = TProtocol, OutputProtocol = void) if (
83 isService!Interface && isTProtocol!InputProtocol &&
84 (isTProtocol!OutputProtocol || is(OutputProtocol == void))
85) {
86 mixin({
87 static if (isDerivedService!Interface) {
88 string code = "class TClient : TClient!(BaseService!Interface, " ~
89 "InputProtocol, OutputProtocol), TClientBase!Interface {\n";
90 code ~= q{
91 this(IProt iprot, OProt oprot) {
92 super(iprot, oprot);
93 }
94
95 static if (is(IProt == OProt)) {
96 this(IProt prot) {
97 super(prot);
98 }
99 }
100
101 // DMD @@BUG@@: If these are not present in this class (would be)
102 // inherited anyway, »not implemented« errors are raised.
103 override IProt inputProtocol() @property {
104 return super.inputProtocol;
105 }
106 override OProt outputProtocol() @property {
107 return super.outputProtocol;
108 }
109 };
110 } else {
111 string code = "class TClient : TClientBase!Interface {";
112 code ~= q{
113 alias InputProtocol IProt;
114 static if (isTProtocol!OutputProtocol) {
115 alias OutputProtocol OProt;
116 } else {
117 static assert(is(OutputProtocol == void));
118 alias InputProtocol OProt;
119 }
120
121 this(IProt iprot, OProt oprot) {
122 iprot_ = iprot;
123 oprot_ = oprot;
124 }
125
126 static if (is(IProt == OProt)) {
127 this(IProt prot) {
128 this(prot, prot);
129 }
130 }
131
132 IProt inputProtocol() @property {
133 return iprot_;
134 }
135
136 OProt outputProtocol() @property {
137 return oprot_;
138 }
139
140 protected IProt iprot_;
141 protected OProt oprot_;
142 protected int seqid_;
143 };
144 }
145
146 foreach (methodName; __traits(derivedMembers, Interface)) {
147 static if (isSomeFunction!(mixin("Interface." ~ methodName))) {
148 bool methodMetaFound;
149 TMethodMeta methodMeta;
150 static if (is(typeof(Interface.methodMeta) : TMethodMeta[])) {
151 enum meta = find!`a.name == b`(Interface.methodMeta, methodName);
152 if (!meta.empty) {
153 methodMetaFound = true;
154 methodMeta = meta.front;
155 }
156 }
157
158 // Generate the code for sending.
159 string[] paramList;
160 string paramAssignCode;
161 foreach (i, _; ParameterTypeTuple!(mixin("Interface." ~ methodName))) {
162 // Use the param name speficied in the meta information if any –
163 // just cosmetics in this case.
164 string paramName;
165 if (methodMetaFound && i < methodMeta.params.length) {
166 paramName = methodMeta.params[i].name;
167 } else {
168 paramName = "param" ~ to!string(i + 1);
169 }
170
171 immutable storage = ParameterStorageClassTuple!(
172 mixin("Interface." ~ methodName))[i];
173 paramList ~= ((storage & ParameterStorageClass.ref_) ? "ref " : "") ~
174 "ParameterTypeTuple!(Interface." ~ methodName ~ ")[" ~
175 to!string(i) ~ "] " ~ paramName;
176 paramAssignCode ~= "args." ~ paramName ~ " = &" ~ paramName ~ ";\n";
177 }
178 code ~= "ReturnType!(Interface." ~ methodName ~ ") " ~ methodName ~
179 "(" ~ ctfeJoin(paramList) ~ ") {\n";
180
181 code ~= "immutable methodName = `" ~ methodName ~ "`;\n";
182
183 immutable paramStructType =
184 "TPargsStruct!(Interface, `" ~ methodName ~ "`)";
185 code ~= paramStructType ~ " args = " ~ paramStructType ~ "();\n";
186 code ~= paramAssignCode;
187 code ~= "oprot_.writeMessageBegin(TMessage(`" ~ methodName ~
188 "`, TMessageType.CALL, ++seqid_));\n";
189 code ~= "args.write(oprot_);\n";
190 code ~= "oprot_.writeMessageEnd();\n";
191 code ~= "oprot_.transport.flush();\n";
192
193 // If this is not a oneway method, generate the recieving code.
194 if (!methodMetaFound || methodMeta.type != TMethodType.ONEWAY) {
195 code ~= "TPresultStruct!(Interface, `" ~ methodName ~ "`) result;\n";
196
197 if (!is(ReturnType!(mixin("Interface." ~ methodName)) == void)) {
198 code ~= "ReturnType!(Interface." ~ methodName ~ ") _return;\n";
199 code ~= "result.success = &_return;\n";
200 }
201
202 // TODO: The C++ implementation checks for matching method name here,
203 // should we do as well?
204 code ~= q{
205 auto msg = iprot_.readMessageBegin();
206 scope (exit) {
207 iprot_.readMessageEnd();
208 iprot_.transport.readEnd();
209 }
210
211 if (msg.type == TMessageType.EXCEPTION) {
212 auto x = new TApplicationException(null);
213 x.read(iprot_);
214 iprot_.transport.readEnd();
215 throw x;
216 }
217 if (msg.type != TMessageType.REPLY) {
218 skip(iprot_, TType.STRUCT);
219 iprot_.transport.readEnd();
220 }
221 if (msg.seqid != seqid_) {
222 throw new TApplicationException(
223 methodName ~ " failed: Out of sequence response.",
224 TApplicationException.Type.BAD_SEQUENCE_ID
225 );
226 }
227 result.read(iprot_);
228 };
229
230 if (methodMetaFound) {
231 foreach (e; methodMeta.exceptions) {
232 code ~= "if (result.isSet!`" ~ e.name ~ "`) throw result." ~
233 e.name ~ ";\n";
234 }
235 }
236
237 if (!is(ReturnType!(mixin("Interface." ~ methodName)) == void)) {
238 code ~= q{
239 if (result.isSet!`success`) return _return;
240 throw new TApplicationException(
241 methodName ~ " failed: Unknown result.",
242 TApplicationException.Type.MISSING_RESULT
243 );
244 };
245 }
246 }
247 code ~= "}\n";
248 }
249 }
250
251 code ~= "}\n";
252 return code;
253 }());
254}
255
256/**
257 * TClient construction helper to avoid having to explicitly specify
258 * the protocol types, i.e. to allow the constructor being called using IFTI
259 * (see $(DMDBUG 6082, D Bugzilla enhancement requet 6082)).
260 */
261TClient!(Interface, Prot) tClient(Interface, Prot)(Prot prot) if (
262 isService!Interface && isTProtocol!Prot
263) {
264 return new TClient!(Interface, Prot)(prot);
265}
266
267/// Ditto
268TClient!(Interface, IProt, Oprot) tClient(Interface, IProt, OProt)
269 (IProt iprot, OProt oprot) if (
270 isService!Interface && isTProtocol!IProt && isTProtocol!OProt
271) {
272 return new TClient!(Interface, IProt, OProt)(iprot, oprot);
273}
274
275/**
276 * Represents the arguments of a Thrift method call, as pointers to the (const)
277 * parameter type to avoid copying.
278 *
279 * There should usually be no reason to use this struct directly without the
280 * help of TClient, but it is documented publicly to help debugging in case
281 * of CTFE errors.
282 *
283 * Consider this example:
284 * ---
285 * interface Foo {
286 * int bar(string a, bool b);
287 *
288 * enum methodMeta = [
289 * TMethodMeta("bar", [TParamMeta("a", 1), TParamMeta("b", 2)])
290 * ];
291 * }
292 *
293 * alias TPargsStruct!(Foo, "bar") FooBarPargs;
294 * ---
295 *
296 * The definition of FooBarPargs is equivalent to (ignoring the necessary
297 * metadata to assign the field IDs):
298 * ---
299 * struct FooBarPargs {
300 * const(string)* a;
301 * const(bool)* b;
302 *
303 * void write(Protocol)(Protocol proto) const if (isTProtocol!Protocol);
304 * }
305 * ---
306 */
307template TPargsStruct(Interface, string methodName) {
308 static assert(is(typeof(mixin("Interface." ~ methodName))),
309 "Could not find method '" ~ methodName ~ "' in '" ~ Interface.stringof ~ "'.");
310 mixin({
311 bool methodMetaFound;
312 TMethodMeta methodMeta;
313 static if (is(typeof(Interface.methodMeta) : TMethodMeta[])) {
314 auto meta = find!`a.name == b`(Interface.methodMeta, methodName);
315 if (!meta.empty) {
316 methodMetaFound = true;
317 methodMeta = meta.front;
318 }
319 }
320
321 string memberCode;
322 string[] fieldMetaCodes;
323 foreach (i, _; ParameterTypeTuple!(mixin("Interface." ~ methodName))) {
324 // If we have no meta information, just use param1, param2, etc. as
325 // field names, it shouldn't really matter anyway. 1-based »indexing«
326 // is used to match the common scheme in the Thrift world.
327 string memberId;
328 string memberName;
329 if (methodMetaFound && i < methodMeta.params.length) {
330 memberId = to!string(methodMeta.params[i].id);
331 memberName = methodMeta.params[i].name;
332 } else {
333 memberId = to!string(i + 1);
334 memberName = "param" ~ to!string(i + 1);
335 }
336
337 // Workaround for DMD @@BUG@@ 6056: make an intermediary alias for the
338 // parameter type, and declare the member using const(memberNameType)*.
339 memberCode ~= "alias ParameterTypeTuple!(Interface." ~ methodName ~
340 ")[" ~ to!string(i) ~ "] " ~ memberName ~ "Type;\n";
341 memberCode ~= "const(" ~ memberName ~ "Type)* " ~ memberName ~ ";\n";
342
343 fieldMetaCodes ~= "TFieldMeta(`" ~ memberName ~ "`, " ~ memberId ~
344 ", TReq.OPT_IN_REQ_OUT)";
345 }
346
347 string code = "struct TPargsStruct {\n";
348 code ~= memberCode;
349 version (TVerboseCodegen) {
350 if (!methodMetaFound &&
351 ParameterTypeTuple!(mixin("Interface." ~ methodName)).length > 0)
352 {
353 code ~= "pragma(msg, `[thrift.codegen.base.TPargsStruct] Warning: No " ~
354 "meta information for method '" ~ methodName ~ "' in service '" ~
355 Interface.stringof ~ "' found.`);\n";
356 }
357 }
358 code ~= "void write(P)(P proto) const if (isTProtocol!P) {\n";
359 code ~= "writeStruct!(typeof(this), P, [" ~ ctfeJoin(fieldMetaCodes) ~
360 "], true)(this, proto);\n";
361 code ~= "}\n";
362 code ~= "}\n";
363 return code;
364 }());
365}
366
367/**
368 * Represents the result of a Thrift method call, using a pointer to the return
369 * value to avoid copying.
370 *
371 * There should usually be no reason to use this struct directly without the
372 * help of TClient, but it is documented publicly to help debugging in case
373 * of CTFE errors.
374 *
375 * Consider this example:
376 * ---
377 * interface Foo {
378 * int bar(string a);
379 *
380 * alias .FooException FooException;
381 *
382 * enum methodMeta = [
383 * TMethodMeta("bar",
384 * [TParamMeta("a", 1)],
385 * [TExceptionMeta("fooe", 1, "FooException")]
386 * )
387 * ];
388 * }
389 * alias TPresultStruct!(Foo, "bar") FooBarPresult;
390 * ---
391 *
392 * The definition of FooBarPresult is equivalent to (ignoring the necessary
393 * metadata to assign the field IDs):
394 * ---
395 * struct FooBarPresult {
396 * int* success;
397 * Foo.FooException fooe;
398 *
399 * struct IsSetFlags {
400 * bool success;
401 * }
402 * IsSetFlags isSetFlags;
403 *
404 * bool isSet(string fieldName)() const @property;
405 * void read(Protocol)(Protocol proto) if (isTProtocol!Protocol);
406 * }
407 * ---
408 */
409template TPresultStruct(Interface, string methodName) {
410 static assert(is(typeof(mixin("Interface." ~ methodName))),
411 "Could not find method '" ~ methodName ~ "' in '" ~ Interface.stringof ~ "'.");
412
413 mixin({
414 string code = "struct TPresultStruct {\n";
415
416 string[] fieldMetaCodes;
417
418 alias ReturnType!(mixin("Interface." ~ methodName)) ResultType;
419 static if (!is(ResultType == void)) {
420 code ~= q{
421 ReturnType!(mixin("Interface." ~ methodName))* success;
422 };
423 fieldMetaCodes ~= "TFieldMeta(`success`, 0, TReq.OPTIONAL)";
424
425 static if (!isNullable!ResultType) {
426 code ~= q{
427 struct IsSetFlags {
428 bool success;
429 }
430 IsSetFlags isSetFlags;
431 };
432 fieldMetaCodes ~= "TFieldMeta(`isSetFlags`, 0, TReq.IGNORE)";
433 }
434 }
435
436 bool methodMetaFound;
437 static if (is(typeof(Interface.methodMeta) : TMethodMeta[])) {
438 auto meta = find!`a.name == b`(Interface.methodMeta, methodName);
439 if (!meta.empty) {
440 foreach (e; meta.front.exceptions) {
441 code ~= "Interface." ~ e.type ~ " " ~ e.name ~ ";\n";
442 fieldMetaCodes ~= "TFieldMeta(`" ~ e.name ~ "`, " ~ to!string(e.id) ~
443 ", TReq.OPTIONAL)";
444 }
445 methodMetaFound = true;
446 }
447 }
448
449 version (TVerboseCodegen) {
450 if (!methodMetaFound &&
451 ParameterTypeTuple!(mixin("Interface." ~ methodName)).length > 0)
452 {
453 code ~= "pragma(msg, `[thrift.codegen.base.TPresultStruct] Warning: No " ~
454 "meta information for method '" ~ methodName ~ "' in service '" ~
455 Interface.stringof ~ "' found.`);\n";
456 }
457 }
458
459 code ~= q{
460 bool isSet(string fieldName)() const @property if (
461 is(MemberType!(typeof(this), fieldName))
462 ) {
463 static if (fieldName == "success") {
464 static if (isNullable!(typeof(*success))) {
465 return *success !is null;
466 } else {
467 return isSetFlags.success;
468 }
469 } else {
470 // We are dealing with an exception member, which, being a nullable
471 // type (exceptions are always classes), has no isSet flag.
472 return __traits(getMember, this, fieldName) !is null;
473 }
474 }
475 };
476
477 code ~= "void read(P)(P proto) if (isTProtocol!P) {\n";
478 code ~= "readStruct!(typeof(this), P, [" ~ ctfeJoin(fieldMetaCodes) ~
479 "], true)(this, proto);\n";
480 code ~= "}\n";
481 code ~= "}\n";
482 return code;
483 }());
484}