blob: 117b076608cf83a77c10678441aa29385d8c9c92 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
module thrift.codegen.client;
import std.algorithm : find;
import std.array : empty, front;
import std.conv : to;
import std.traits : isSomeFunction, ParameterStorageClass,
ParameterStorageClassTuple, ParameterTypeTuple, ReturnType;
import thrift.codegen.base;
import thrift.internal.codegen;
import thrift.internal.ctfe;
import thrift.protocol.base;
/**
* Thrift service client, which implements an interface by synchronously
* calling a server over a TProtocol.
*
* TClientBase simply extends Interface with generic input/output protocol
* properties to serve as a supertype for all TClients for the same service,
* which might be instantiated with different concrete protocol types (there
* is no covariance for template type parameters). If Interface is derived
* from another interface BaseInterface, it also extends
* TClientBase!BaseInterface.
*
* TClient is the class that actually implements TClientBase. Just as
* TClientBase, it is also derived from TClient!BaseInterface for inheriting
* services.
*
* TClient takes two optional template arguments which can be used for
* specifying the actual TProtocol implementation used for optimization
* purposes, as virtual calls can completely be eliminated then. If
* OutputProtocol is not specified, it is assumed to be the same as
* InputProtocol. The protocol properties defined by TClientBase are exposed
* with their concrete type (return type covariance).
*
* In addition to implementing TClientBase!Interface, TClient offers the
* following constructors:
* ---
* this(InputProtocol iprot, OutputProtocol oprot);
* // Only if is(InputProtocol == OutputProtocol), to use the same protocol
* // for both input and output:
* this(InputProtocol prot);
* ---
*
* The sequence id of the method calls starts at zero and is automatically
* incremented.
*/
interface TClientBase(Interface) if (isBaseService!Interface) : Interface {
/**
* The input protocol used by the client.
*/
TProtocol inputProtocol() @property;
/**
* The output protocol used by the client.
*/
TProtocol outputProtocol() @property;
}
/// Ditto
interface TClientBase(Interface) if (isDerivedService!Interface) :
TClientBase!(BaseService!Interface), Interface {}
/// Ditto
template TClient(Interface, InputProtocol = TProtocol, OutputProtocol = void) if (
isService!Interface && isTProtocol!InputProtocol &&
(isTProtocol!OutputProtocol || is(OutputProtocol == void))
) {
mixin({
static if (isDerivedService!Interface) {
string code = "class TClient : TClient!(BaseService!Interface, " ~
"InputProtocol, OutputProtocol), TClientBase!Interface {\n";
code ~= q{
this(IProt iprot, OProt oprot) {
super(iprot, oprot);
}
static if (is(IProt == OProt)) {
this(IProt prot) {
super(prot);
}
}
// DMD @@BUG@@: If these are not present in this class (would be)
// inherited anyway, »not implemented« errors are raised.
override IProt inputProtocol() @property {
return super.inputProtocol;
}
override OProt outputProtocol() @property {
return super.outputProtocol;
}
};
} else {
string code = "class TClient : TClientBase!Interface {";
code ~= q{
alias InputProtocol IProt;
static if (isTProtocol!OutputProtocol) {
alias OutputProtocol OProt;
} else {
static assert(is(OutputProtocol == void));
alias InputProtocol OProt;
}
this(IProt iprot, OProt oprot) {
iprot_ = iprot;
oprot_ = oprot;
}
static if (is(IProt == OProt)) {
this(IProt prot) {
this(prot, prot);
}
}
IProt inputProtocol() @property {
return iprot_;
}
OProt outputProtocol() @property {
return oprot_;
}
protected IProt iprot_;
protected OProt oprot_;
protected int seqid_;
};
}
foreach (methodName; __traits(derivedMembers, Interface)) {
static if (isSomeFunction!(mixin("Interface." ~ methodName))) {
bool methodMetaFound;
TMethodMeta methodMeta;
static if (is(typeof(Interface.methodMeta) : TMethodMeta[])) {
enum meta = find!`a.name == b`(Interface.methodMeta, methodName);
if (!meta.empty) {
methodMetaFound = true;
methodMeta = meta.front;
}
}
// Generate the code for sending.
string[] paramList;
string paramAssignCode;
foreach (i, _; ParameterTypeTuple!(mixin("Interface." ~ methodName))) {
// Use the param name speficied in the meta information if any –
// just cosmetics in this case.
string paramName;
if (methodMetaFound && i < methodMeta.params.length) {
paramName = methodMeta.params[i].name;
} else {
paramName = "param" ~ to!string(i + 1);
}
immutable storage = ParameterStorageClassTuple!(
mixin("Interface." ~ methodName))[i];
paramList ~= ((storage & ParameterStorageClass.ref_) ? "ref " : "") ~
"ParameterTypeTuple!(Interface." ~ methodName ~ ")[" ~
to!string(i) ~ "] " ~ paramName;
paramAssignCode ~= "args." ~ paramName ~ " = &" ~ paramName ~ ";\n";
}
code ~= "ReturnType!(Interface." ~ methodName ~ ") " ~ methodName ~
"(" ~ ctfeJoin(paramList) ~ ") {\n";
code ~= "immutable methodName = `" ~ methodName ~ "`;\n";
immutable paramStructType =
"TPargsStruct!(Interface, `" ~ methodName ~ "`)";
code ~= paramStructType ~ " args = " ~ paramStructType ~ "();\n";
code ~= paramAssignCode;
code ~= "oprot_.writeMessageBegin(TMessage(`" ~ methodName ~ "`, ";
code ~= ((methodMetaFound && methodMeta.type == TMethodType.ONEWAY)
? "TMessageType.ONEWAY" : "TMessageType.CALL");
code ~= ", ++seqid_));\n";
code ~= "args.write(oprot_);\n";
code ~= "oprot_.writeMessageEnd();\n";
code ~= "oprot_.transport.flush();\n";
// If this is not a oneway method, generate the receiving code.
if (!methodMetaFound || methodMeta.type != TMethodType.ONEWAY) {
code ~= "TPresultStruct!(Interface, `" ~ methodName ~ "`) result;\n";
if (!is(ReturnType!(mixin("Interface." ~ methodName)) == void)) {
code ~= "ReturnType!(Interface." ~ methodName ~ ") _return;\n";
code ~= "result.success = &_return;\n";
}
// TODO: The C++ implementation checks for matching method name here,
// should we do as well?
code ~= q{
auto msg = iprot_.readMessageBegin();
scope (exit) {
iprot_.readMessageEnd();
iprot_.transport.readEnd();
}
if (msg.type == TMessageType.EXCEPTION) {
auto x = new TApplicationException(null);
x.read(iprot_);
iprot_.transport.readEnd();
throw x;
}
if (msg.type != TMessageType.REPLY) {
skip(iprot_, TType.STRUCT);
iprot_.transport.readEnd();
}
if (msg.seqid != seqid_) {
throw new TApplicationException(
methodName ~ " failed: Out of sequence response.",
TApplicationException.Type.BAD_SEQUENCE_ID
);
}
result.read(iprot_);
};
if (methodMetaFound) {
foreach (e; methodMeta.exceptions) {
code ~= "if (result.isSet!`" ~ e.name ~ "`) throw result." ~
e.name ~ ";\n";
}
}
if (!is(ReturnType!(mixin("Interface." ~ methodName)) == void)) {
code ~= q{
if (result.isSet!`success`) return _return;
throw new TApplicationException(
methodName ~ " failed: Unknown result.",
TApplicationException.Type.MISSING_RESULT
);
};
}
}
code ~= "}\n";
}
}
code ~= "}\n";
return code;
}());
}
/**
* TClient construction helper to avoid having to explicitly specify
* the protocol types, i.e. to allow the constructor being called using IFTI
* (see $(DMDBUG 6082, D Bugzilla enhancement requet 6082)).
*/
TClient!(Interface, Prot) tClient(Interface, Prot)(Prot prot) if (
isService!Interface && isTProtocol!Prot
) {
return new TClient!(Interface, Prot)(prot);
}
/// Ditto
TClient!(Interface, IProt, Oprot) tClient(Interface, IProt, OProt)
(IProt iprot, OProt oprot) if (
isService!Interface && isTProtocol!IProt && isTProtocol!OProt
) {
return new TClient!(Interface, IProt, OProt)(iprot, oprot);
}
/**
* Represents the arguments of a Thrift method call, as pointers to the (const)
* parameter type to avoid copying.
*
* There should usually be no reason to use this struct directly without the
* help of TClient, but it is documented publicly to help debugging in case
* of CTFE errors.
*
* Consider this example:
* ---
* interface Foo {
* int bar(string a, bool b);
*
* enum methodMeta = [
* TMethodMeta("bar", [TParamMeta("a", 1), TParamMeta("b", 2)])
* ];
* }
*
* alias TPargsStruct!(Foo, "bar") FooBarPargs;
* ---
*
* The definition of FooBarPargs is equivalent to (ignoring the necessary
* metadata to assign the field IDs):
* ---
* struct FooBarPargs {
* const(string)* a;
* const(bool)* b;
*
* void write(Protocol)(Protocol proto) const if (isTProtocol!Protocol);
* }
* ---
*/
template TPargsStruct(Interface, string methodName) {
static assert(is(typeof(mixin("Interface." ~ methodName))),
"Could not find method '" ~ methodName ~ "' in '" ~ Interface.stringof ~ "'.");
mixin({
bool methodMetaFound;
TMethodMeta methodMeta;
static if (is(typeof(Interface.methodMeta) : TMethodMeta[])) {
auto meta = find!`a.name == b`(Interface.methodMeta, methodName);
if (!meta.empty) {
methodMetaFound = true;
methodMeta = meta.front;
}
}
string memberCode;
string[] fieldMetaCodes;
foreach (i, _; ParameterTypeTuple!(mixin("Interface." ~ methodName))) {
// If we have no meta information, just use param1, param2, etc. as
// field names, it shouldn't really matter anyway. 1-based »indexing«
// is used to match the common scheme in the Thrift world.
string memberId;
string memberName;
if (methodMetaFound && i < methodMeta.params.length) {
memberId = to!string(methodMeta.params[i].id);
memberName = methodMeta.params[i].name;
} else {
memberId = to!string(i + 1);
memberName = "param" ~ to!string(i + 1);
}
// Workaround for DMD @@BUG@@ 6056: make an intermediary alias for the
// parameter type, and declare the member using const(memberNameType)*.
memberCode ~= "alias ParameterTypeTuple!(Interface." ~ methodName ~
")[" ~ to!string(i) ~ "] " ~ memberName ~ "Type;\n";
memberCode ~= "const(" ~ memberName ~ "Type)* " ~ memberName ~ ";\n";
fieldMetaCodes ~= "TFieldMeta(`" ~ memberName ~ "`, " ~ memberId ~
", TReq.OPT_IN_REQ_OUT)";
}
string code = "struct TPargsStruct {\n";
code ~= memberCode;
version (TVerboseCodegen) {
if (!methodMetaFound &&
ParameterTypeTuple!(mixin("Interface." ~ methodName)).length > 0)
{
code ~= "pragma(msg, `[thrift.codegen.base.TPargsStruct] Warning: No " ~
"meta information for method '" ~ methodName ~ "' in service '" ~
Interface.stringof ~ "' found.`);\n";
}
}
code ~= "void write(P)(P proto) const if (isTProtocol!P) {\n";
code ~= "writeStruct!(typeof(this), P, [" ~ ctfeJoin(fieldMetaCodes) ~
"], true)(this, proto);\n";
code ~= "}\n";
code ~= "}\n";
return code;
}());
}
/**
* Represents the result of a Thrift method call, using a pointer to the return
* value to avoid copying.
*
* There should usually be no reason to use this struct directly without the
* help of TClient, but it is documented publicly to help debugging in case
* of CTFE errors.
*
* Consider this example:
* ---
* interface Foo {
* int bar(string a);
*
* alias .FooException FooException;
*
* enum methodMeta = [
* TMethodMeta("bar",
* [TParamMeta("a", 1)],
* [TExceptionMeta("fooe", 1, "FooException")]
* )
* ];
* }
* alias TPresultStruct!(Foo, "bar") FooBarPresult;
* ---
*
* The definition of FooBarPresult is equivalent to (ignoring the necessary
* metadata to assign the field IDs):
* ---
* struct FooBarPresult {
* int* success;
* Foo.FooException fooe;
*
* struct IsSetFlags {
* bool success;
* }
* IsSetFlags isSetFlags;
*
* bool isSet(string fieldName)() const @property;
* void read(Protocol)(Protocol proto) if (isTProtocol!Protocol);
* }
* ---
*/
template TPresultStruct(Interface, string methodName) {
static assert(is(typeof(mixin("Interface." ~ methodName))),
"Could not find method '" ~ methodName ~ "' in '" ~ Interface.stringof ~ "'.");
mixin({
string code = "struct TPresultStruct {\n";
string[] fieldMetaCodes;
alias ReturnType!(mixin("Interface." ~ methodName)) ResultType;
static if (!is(ResultType == void)) {
code ~= q{
ReturnType!(mixin("Interface." ~ methodName))* success;
};
fieldMetaCodes ~= "TFieldMeta(`success`, 0, TReq.OPTIONAL)";
static if (!isNullable!ResultType) {
code ~= q{
struct IsSetFlags {
bool success;
}
IsSetFlags isSetFlags;
};
fieldMetaCodes ~= "TFieldMeta(`isSetFlags`, 0, TReq.IGNORE)";
}
}
bool methodMetaFound;
static if (is(typeof(Interface.methodMeta) : TMethodMeta[])) {
auto meta = find!`a.name == b`(Interface.methodMeta, methodName);
if (!meta.empty) {
foreach (e; meta.front.exceptions) {
code ~= "Interface." ~ e.type ~ " " ~ e.name ~ ";\n";
fieldMetaCodes ~= "TFieldMeta(`" ~ e.name ~ "`, " ~ to!string(e.id) ~
", TReq.OPTIONAL)";
}
methodMetaFound = true;
}
}
version (TVerboseCodegen) {
if (!methodMetaFound &&
ParameterTypeTuple!(mixin("Interface." ~ methodName)).length > 0)
{
code ~= "pragma(msg, `[thrift.codegen.base.TPresultStruct] Warning: No " ~
"meta information for method '" ~ methodName ~ "' in service '" ~
Interface.stringof ~ "' found.`);\n";
}
}
code ~= q{
bool isSet(string fieldName)() const @property if (
is(MemberType!(typeof(this), fieldName))
) {
static if (fieldName == "success") {
static if (isNullable!(typeof(*success))) {
return *success !is null;
} else {
return isSetFlags.success;
}
} else {
// We are dealing with an exception member, which, being a nullable
// type (exceptions are always classes), has no isSet flag.
return __traits(getMember, this, fieldName) !is null;
}
}
};
code ~= "void read(P)(P proto) if (isTProtocol!P) {\n";
code ~= "readStruct!(typeof(this), P, [" ~ ctfeJoin(fieldMetaCodes) ~
"], true)(this, proto);\n";
code ~= "}\n";
code ~= "}\n";
return code;
}());
}