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.client; 20 21 import std.algorithm : find; 22 import std.array : empty, front; 23 import std.conv : to; 24 import std.traits : isSomeFunction, ParameterStorageClass, 25 ParameterStorageClassTuple, ParameterTypeTuple, ReturnType; 26 import thrift.codegen.base; 27 import thrift.internal.codegen; 28 import thrift.internal.ctfe; 29 import 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 */ 65 interface 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 78 interface TClientBase(Interface) if (isDerivedService!Interface) : 79 TClientBase!(BaseService!Interface), Interface {} 80 81 /// Ditto 82 template 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 */ 261 TClient!(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 268 TClient!(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 */ 307 template 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 */ 409 template 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 }