You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@thrift.apache.org by jf...@apache.org on 2012/03/22 22:49:13 UTC
svn commit: r1304085 [3/10] - in /thrift/trunk: ./ aclocal/ compiler/cpp/
compiler/cpp/src/generate/ lib/ lib/d/ lib/d/src/ lib/d/src/thrift/
lib/d/src/thrift/async/ lib/d/src/thrift/codegen/
lib/d/src/thrift/internal/ lib/d/src/thrift/internal/test/ l...
Added: thrift/trunk/lib/d/src/thrift/codegen/base.d
URL: http://svn.apache.org/viewvc/thrift/trunk/lib/d/src/thrift/codegen/base.d?rev=1304085&view=auto
==============================================================================
--- thrift/trunk/lib/d/src/thrift/codegen/base.d (added)
+++ thrift/trunk/lib/d/src/thrift/codegen/base.d Thu Mar 22 21:49:10 2012
@@ -0,0 +1,950 @@
+/*
+ * 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.
+ */
+
+/**
+ * Code generation metadata and templates used for implementing struct
+ * serialization.
+ *
+ * Many templates can be customized using field meta data, which is read from
+ * a manifest constant member of the given type called fieldMeta (if present),
+ * and is concatenated with the elements from the optional fieldMetaData
+ * template alias parameter.
+ *
+ * Some code generation templates take account of the optional TVerboseCodegen
+ * version declaration, which causes warning messages to be emitted if no
+ * metadata for a field/method has been found and the default behavior is
+ * used instead. If this version is not defined, the templates just silently
+ * behave like the Thrift compiler does in this situation, i.e. automatically
+ * assign negative ids (starting at -1) for fields and assume TReq.AUTO as
+ * requirement level.
+ */
+// Implementation note: All the templates in here taking a field metadata
+// parameter should ideally have a constraint that restricts the alias to
+// TFieldMeta[]-typed values, but the is() expressions seems to always fail.
+module thrift.codegen.base;
+
+import std.algorithm : find;
+import std.array : empty, front;
+import std.conv : to;
+import std.exception : enforce;
+import std.traits : BaseTypeTuple, isPointer, isSomeFunction, pointerTarget,
+ ReturnType;
+import thrift.base;
+import thrift.internal.codegen;
+import thrift.protocol.base;
+
+/*
+ * Thrift struct/service meta data, which is used to store information from
+ * the interface definition files not representable in plain D, i.e. field
+ * requirement levels, Thrift field IDs, etc.
+ */
+
+/**
+ * Struct field requirement levels.
+ */
+enum TReq {
+ /// Detect the requiredness from the field type: if it is nullable, treat
+ /// the field as optional, if it is non-nullable, treat the field as
+ /// required. This is the default used for handling structs not generated
+ /// from an IDL file, and never emitted by the Thrift compiler. TReq.AUTO
+ /// shouldn't be specified explicitly.
+ // Implementation note: thrift.codegen templates use
+ // thrift.internal.codegen.memberReq to resolve AUTO to REQUIRED/OPTIONAL
+ // instead of handling it directly.
+ AUTO,
+
+ /// The field is treated as optional when deserializing/receiving the struct
+ /// and as required when serializing/sending. This is the Thrift default if
+ /// neither "required" nor "optional" are specified in the IDL file.
+ OPT_IN_REQ_OUT,
+
+ /// The field is optional.
+ OPTIONAL,
+
+ /// The field is required.
+ REQUIRED,
+
+ /// Ignore the struct field when serializing/deserializing.
+ IGNORE
+}
+
+/**
+ * The way how methods are called.
+ */
+enum TMethodType {
+ /// Called in the normal two-way scheme consisting of a request and a
+ /// response.
+ REGULAR,
+
+ /// A fire-and-forget one-way method, where no response is sent and the
+ /// client immediately returns.
+ ONEWAY
+}
+
+/**
+ * Compile-time metadata for a struct field.
+ */
+struct TFieldMeta {
+ /// The name of the field. Used for matching a TFieldMeta with the actual
+ /// D struct member during code generation.
+ string name;
+
+ /// The (Thrift) id of the field.
+ short id;
+
+ /// Whether the field is requried.
+ TReq req;
+
+ /// A code string containing a D expression for the default value, if there
+ /// is one.
+ string defaultValue;
+}
+
+/**
+ * Compile-time metadata for a service method.
+ */
+struct TMethodMeta {
+ /// The name of the method. Used for matching a TMethodMeta with the actual
+ /// method during code generation.
+ string name;
+
+ /// Meta information for the parameteres.
+ TParamMeta[] params;
+
+ /// Specifies which exceptions can be thrown by the method. All other
+ /// exceptions are converted to a TApplicationException instead.
+ TExceptionMeta[] exceptions;
+
+ /// The fundamental type of the method.
+ TMethodType type;
+}
+
+/**
+ * Compile-time metadata for a service method parameter.
+ */
+struct TParamMeta {
+ /// The name of the parameter. Contrary to TFieldMeta, it only serves
+ /// decorative purposes here.
+ string name;
+
+ /// The Thrift id of the parameter in the param struct.
+ short id;
+
+ /// A code string containing a D expression for the default value for the
+ /// parameter, if any.
+ string defaultValue;
+}
+
+/**
+ * Compile-time metadata for a service method exception annotation.
+ */
+struct TExceptionMeta {
+ /// The name of the exception »return value«. Contrary to TFieldMeta, it
+ /// only serves decorative purposes here, as it is only used in code not
+ /// visible to processor implementations/service clients.
+ string name;
+
+ /// The Thrift id of the exception field in the return value struct.
+ short id;
+
+ /// The name of the exception type.
+ string type;
+}
+
+/**
+ * A pair of two TPorotocols. To be used in places where a list of protocols
+ * is expected, for specifying different protocols for input and output.
+ */
+struct TProtocolPair(InputProtocol, OutputProtocol) if (
+ isTProtocol!InputProtocol && isTProtocol!OutputProtocol
+) {}
+
+/**
+ * true if T is a TProtocolPair.
+ */
+template isTProtocolPair(T) {
+ static if (is(T _ == TProtocolPair!(I, O), I, O)) {
+ enum isTProtocolPair = true;
+ } else {
+ enum isTProtocolPair = false;
+ }
+}
+
+unittest {
+ static assert(isTProtocolPair!(TProtocolPair!(TProtocol, TProtocol)));
+ static assert(!isTProtocolPair!TProtocol);
+}
+
+/**
+ * true if T is a TProtocol or a TProtocolPair.
+ */
+template isTProtocolOrPair(T) {
+ enum isTProtocolOrPair = isTProtocol!T || isTProtocolPair!T;
+}
+
+unittest {
+ static assert(isTProtocolOrPair!TProtocol);
+ static assert(isTProtocolOrPair!(TProtocolPair!(TProtocol, TProtocol)));
+ static assert(!isTProtocolOrPair!void);
+}
+
+/**
+ * true if T represents a Thrift service.
+ */
+template isService(T) {
+ enum isService = isBaseService!T || isDerivedService!T;
+}
+
+/**
+ * true if T represents a Thrift service not derived from another service.
+ */
+template isBaseService(T) {
+ static if(is(T _ == interface) &&
+ (!is(T TBases == super) || TBases.length == 0)
+ ) {
+ enum isBaseService = true;
+ } else {
+ enum isBaseService = false;
+ }
+}
+
+/**
+ * true if T represents a Thrift service derived from another service.
+ */
+template isDerivedService(T) {
+ static if(is(T _ == interface) &&
+ is(T TBases == super) && TBases.length == 1
+ ) {
+ enum isDerivedService = isService!(TBases[0]);
+ } else {
+ enum isDerivedService = false;
+ }
+}
+
+/**
+ * For derived services, gets the base service interface.
+ */
+template BaseService(T) if (isDerivedService!T) {
+ alias BaseTypeTuple!T[0] BaseService;
+}
+
+
+/*
+ * Code generation templates.
+ */
+
+/**
+ * Mixin template defining additional helper methods for using a struct with
+ * Thrift, and a member called isSetFlags if the struct contains any fields
+ * for which an »is set« flag is needed.
+ *
+ * It can only be used inside structs or Exception classes.
+ *
+ * For example, consider the following struct definition:
+ * ---
+ * struct Foo {
+ * string a;
+ * int b;
+ * int c;
+ *
+ * mixin TStructHelpers!([
+ * TFieldMeta("a", 1), // Implicitly optional (nullable).
+ * TFieldMeta("b", 2), // Implicitly required (non-nullable).
+ * TFieldMeta("c", 3, TReq.REQUIRED, "4")
+ * ]);
+ * }
+ * ---
+ *
+ * TStructHelper adds the following methods to the struct:
+ * ---
+ * /++
+ * + Sets member fieldName to the given value and marks it as set.
+ * +
+ * + Examples:
+ * + ---
+ * + auto f = Foo();
+ * + f.set!"b"(12345);
+ * + assert(f.isSet!"b");
+ * + ---
+ * +/
+ * void set(string fieldName)(MemberType!(This, fieldName) value);
+ *
+ * /++
+ * + Resets member fieldName to the init property of its type and marks it as
+ * + not set.
+ * +
+ * + Examples:
+ * + ---
+ * + // Set f.b to some value.
+ * + auto f = Foo();
+ * + f.set!"b"(12345);
+ * +
+ * + f.unset!b();
+ * +
+ * + // f.b is now unset again.
+ * + assert(!f.isSet!"b");
+ * + ---
+ * +/
+ * void unset(string fieldName)();
+ *
+ * /++
+ * + Returns whether member fieldName is set.
+ * +
+ * + Examples:
+ * + ---
+ * + auto f = Foo();
+ * + assert(!f.isSet!"b");
+ * + f.set!"b"(12345);
+ * + assert(f.isSet!"b");
+ * + ---
+ * +/
+ * bool isSet(string fieldName)() const @property;
+ *
+ * /++
+ * + Returns a string representation of the struct.
+ * +
+ * + Examples:
+ * + ---
+ * + auto f = Foo();
+ * + f.a = "a string";
+ * + assert(f.toString() == `Foo("a string", 0 (unset), 4)`);
+ * + ---
+ * +/
+ * string toString() const;
+ *
+ * /++
+ * + Deserializes the struct, setting its members to the values read from the
+ * + protocol. Forwards to readStruct(this, proto);
+ * +/
+ * void read(Protocol)(Protocol proto) if (isTProtocol!Protocol);
+ *
+ * /++
+ * + Serializes the struct to the target protocol. Forwards to
+ * + writeStruct(this, proto);
+ * +/
+ * void write(Protocol)(Protocol proto) const if (isTProtocol!Protocol);
+ * ---
+ *
+ * Additionally, an opEquals() implementation is provided which simply
+ * compares all fields, but disregards the is set struct, if any (the exact
+ * signature obviously differs between structs and exception classes). The
+ * metadata is stored in a manifest constant called fieldMeta.
+ *
+ * Note: To set the default values for fields where one has been specified in
+ * the field metadata, a parameterless static opCall is generated, because D
+ * does not allow parameterless (default) constructors for structs. Thus, be
+ * always to use to initialize structs:
+ * ---
+ * Foo foo; // Wrong!
+ * auto foo = Foo(); // Correct.
+ * ---
+ */
+mixin template TStructHelpers(alias fieldMetaData = cast(TFieldMeta[])null) if (
+ is(typeof(fieldMetaData) : TFieldMeta[])
+) {
+ import std.algorithm : canFind;
+ import thrift.codegen.base;
+ import thrift.internal.codegen : isNullable, MemberType, mergeFieldMeta,
+ FieldNames;
+ import thrift.protocol.base : TProtocol, isTProtocol;
+
+ alias typeof(this) This;
+ static assert(is(This == struct) || is(This : Exception),
+ "TStructHelpers can only be used inside a struct or an Exception class.");
+
+ static if (is(TIsSetFlags!(This, fieldMetaData))) {
+ // If we need to keep isSet flags around, create an instance of the
+ // container struct.
+ TIsSetFlags!(This, fieldMetaData) isSetFlags;
+ enum fieldMeta = fieldMetaData ~ [TFieldMeta("isSetFlags", 0, TReq.IGNORE)];
+ } else {
+ enum fieldMeta = fieldMetaData;
+ }
+
+ void set(string fieldName)(MemberType!(This, fieldName) value) if (
+ is(MemberType!(This, fieldName))
+ ) {
+ __traits(getMember, this, fieldName) = value;
+ static if (is(typeof(mixin("this.isSetFlags." ~ fieldName)) : bool)) {
+ __traits(getMember, this.isSetFlags, fieldName) = true;
+ }
+ }
+
+ void unset(string fieldName)() if (is(MemberType!(This, fieldName))) {
+ static if (is(typeof(mixin("this.isSetFlags." ~ fieldName)) : bool)) {
+ __traits(getMember, this.isSetFlags, fieldName) = false;
+ }
+ __traits(getMember, this, fieldName) = MemberType!(This, fieldName).init;
+ }
+
+ bool isSet(string fieldName)() const @property if (
+ is(MemberType!(This, fieldName))
+ ) {
+ static if (isNullable!(MemberType!(This, fieldName))) {
+ return __traits(getMember, this, fieldName) !is null;
+ } else static if (is(typeof(mixin("this.isSetFlags." ~ fieldName)) : bool)) {
+ return __traits(getMember, this.isSetFlags, fieldName);
+ } else {
+ // This is a required field, which is always set.
+ return true;
+ }
+ }
+
+ static if (is(This _ == class)) {
+ override string toString() const {
+ return thriftToStringImpl();
+ }
+
+ override bool opEquals(Object other) const {
+ auto rhs = cast(This)other;
+ if (rhs) {
+ return thriftOpEqualsImpl(rhs);
+ }
+
+ return (cast()super).opEquals(other);
+ }
+ } else {
+ string toString() const {
+ return thriftToStringImpl();
+ }
+
+ bool opEquals(ref const This other) const {
+ return thriftOpEqualsImpl(other);
+ }
+ }
+
+ private string thriftToStringImpl() const {
+ import std.conv : to;
+ string result = This.stringof ~ "(";
+ mixin({
+ string code = "";
+ bool first = true;
+ foreach (name; FieldNames!(This, fieldMeta)) {
+ if (first) {
+ first = false;
+ } else {
+ code ~= "result ~= `, `;\n";
+ }
+ code ~= "result ~= `" ~ name ~ ": ` ~ to!string(cast()this." ~ name ~ ");\n";
+ code ~= "if (!isSet!q{" ~ name ~ "}) {\n";
+ code ~= "result ~= ` (unset)`;\n";
+ code ~= "}\n";
+ }
+ return code;
+ }());
+ result ~= ")";
+ return result;
+ }
+
+ private bool thriftOpEqualsImpl(const ref This rhs) const {
+ foreach (name; FieldNames!This) {
+ if (mixin("this." ~ name) != mixin("rhs." ~ name)) return false;
+ }
+ return true;
+ }
+
+ static if (canFind!`!a.defaultValue.empty`(mergeFieldMeta!(This, fieldMetaData))) {
+ static if (is(This _ == class)) {
+ this() {
+ mixin(thriftFieldInitCode!(mergeFieldMeta!(This, fieldMetaData))("this"));
+ }
+ } else {
+ // DMD @@BUG@@: Have to use auto here to avoid »no size yet for forward
+ // reference« errors.
+ static auto opCall() {
+ auto result = This.init;
+ mixin(thriftFieldInitCode!(mergeFieldMeta!(This, fieldMetaData))("result"));
+ return result;
+ }
+ }
+ }
+
+ void read(Protocol)(Protocol proto) if (isTProtocol!Protocol) {
+ // Need to explicitly specify fieldMetaData here, since it isn't already
+ // picked up in some situations (e.g. the TArgs struct for methods with
+ // multiple parameters in async_test_servers) otherwise. Due to a DMD
+ // @@BUG@@, we need to explicitly specify the other template parameters
+ // as well.
+ readStruct!(This, Protocol, fieldMetaData, false)(this, proto);
+ }
+
+ void write(Protocol)(Protocol proto) const if (isTProtocol!Protocol) {
+ writeStruct!(This, Protocol, fieldMetaData, false)(this, proto);
+ }
+}
+
+// DMD @@BUG@@: Having this inside TStructHelpers leads to weird lookup errors
+// (e.g. for std.arry.empty).
+string thriftFieldInitCode(alias fieldMeta)(string thisName) {
+ string code = "";
+ foreach (field; fieldMeta) {
+ if (field.defaultValue.empty) continue;
+ code ~= thisName ~ "." ~ field.name ~ " = " ~ field.defaultValue ~ ";\n";
+ }
+ return code;
+}
+
+version (unittest) {
+ // Cannot make this nested in the unittest block due to a »no size yet for
+ // forward reference« error.
+ struct Foo {
+ string a;
+ int b;
+ int c;
+
+ mixin TStructHelpers!([
+ TFieldMeta("a", 1),
+ TFieldMeta("b", 2, TReq.OPT_IN_REQ_OUT),
+ TFieldMeta("c", 3, TReq.REQUIRED, "4")
+ ]);
+ }
+}
+unittest {
+ auto f = Foo();
+
+ f.set!"b"(12345);
+ assert(f.isSet!"b");
+ f.unset!"b"();
+ assert(!f.isSet!"b");
+ f.set!"b"(12345);
+ assert(f.isSet!"b");
+ f.unset!"b"();
+
+ f.a = "a string";
+ assert(f.toString() == `Foo(a: a string, b: 0 (unset), c: 4)`);
+}
+
+/**
+ * Generates an eponymous struct with boolean flags for the non-required
+ * non-nullable fields of T, if any, or nothing otherwise (i.e. the template
+ * body is empty).
+ *
+ * Nullable fields are just set to null to signal »not set«.
+ *
+ * In most cases, you do not want to use this directly, but via TStructHelpers
+ * instead.
+ */
+// DMD @@BUG@@: Using getFieldMeta!T in here horribly breaks things to the point
+// where getFieldMeta is *instantiated twice*, with different bodies. This is
+// connected to the position of »enum fieldMeta« in TStructHelpers.
+template TIsSetFlags(T, alias fieldMetaData) {
+ mixin({
+ string boolDefinitions;
+ foreach (name; __traits(derivedMembers, T)) {
+ static if (!is(MemberType!(T, name)) || is(MemberType!(T, name) == void)) {
+ // We hit something strange like the TStructHelpers template itself,
+ // just ignore.
+ } else static if (isNullable!(MemberType!(T, name))) {
+ // If the field is nullable, we don't need an isSet flag as we can map
+ // unset to null.
+ } else static if (memberReq!(T, name, fieldMetaData) != TReq.REQUIRED) {
+ boolDefinitions ~= "bool " ~ name ~ ";\n";
+ }
+ }
+ if (!boolDefinitions.empty) {
+ return "struct TIsSetFlags {\n" ~ boolDefinitions ~ "}";
+ } else {
+ return "";
+ }
+ }());
+}
+
+/**
+ * Deserializes a Thrift struct from a protocol.
+ *
+ * Using the Protocol template parameter, the concrete TProtocol to use can be
+ * be specified. If the pointerStruct parameter is set to true, the struct
+ * fields are expected to be pointers to the actual data. This is used
+ * internally (combined with TPResultStruct) and usually should not be used in
+ * user code.
+ *
+ * This is a free function to make it possible to read exisiting structs from
+ * the wire without altering their definitions.
+ */
+void readStruct(T, Protocol, alias fieldMetaData = cast(TFieldMeta[])null,
+ bool pointerStruct = false)(ref T s, Protocol p) if (isTProtocol!Protocol)
+{
+ mixin({
+ string code;
+
+ // Check that all fields for which there is meta info are actually in the
+ // passed struct type.
+ foreach (field; mergeFieldMeta!(T, fieldMetaData)) {
+ code ~= "static assert(is(MemberType!(T, `" ~ field.name ~ "`)));\n";
+ }
+
+ // Returns the code string for reading a value of type F off the wire and
+ // assigning it to v. The level parameter is used to make sure that there
+ // are no conflicting variable names on recursive calls.
+ string readValueCode(ValueType)(string v, size_t level = 0) {
+ // Some non-ambigous names to use (shadowing is not allowed in D).
+ immutable i = "i" ~ to!string(level);
+ immutable elem = "elem" ~ to!string(level);
+ immutable key = "key" ~ to!string(level);
+ immutable list = "list" ~ to!string(level);
+ immutable map = "map" ~ to!string(level);
+ immutable set = "set" ~ to!string(level);
+ immutable value = "value" ~ to!string(level);
+
+ alias FullyUnqual!ValueType F;
+
+ static if (is(F == bool)) {
+ return v ~ " = p.readBool();";
+ } else static if (is(F == byte)) {
+ return v ~ " = p.readByte();";
+ } else static if (is(F == double)) {
+ return v ~ " = p.readDouble();";
+ } else static if (is(F == short)) {
+ return v ~ " = p.readI16();";
+ } else static if (is(F == int)) {
+ return v ~ " = p.readI32();";
+ } else static if (is(F == long)) {
+ return v ~ " = p.readI64();";
+ } else static if (is(F : string)) {
+ return v ~ " = p.readString();";
+ } else static if (is(F == enum)) {
+ return v ~ " = cast(typeof(" ~ v ~ "))p.readI32();";
+ } else static if (is(F _ : E[], E)) {
+ return "{\n" ~
+ "auto " ~ list ~ " = p.readListBegin();\n" ~
+ // TODO: Check element type here?
+ v ~ " = new typeof(" ~ v ~ "[0])[" ~ list ~ ".size];\n" ~
+ "foreach (" ~ i ~ "; 0 .. " ~ list ~ ".size) {\n" ~
+ readValueCode!E(v ~ "[" ~ i ~ "]", level + 1) ~ "\n" ~
+ "}\n" ~
+ "p.readListEnd();\n" ~
+ "}";
+ } else static if (is(F _ : V[K], K, V)) {
+ return "{\n" ~
+ "auto " ~ map ~ " = p.readMapBegin();" ~
+ v ~ " = null;\n" ~
+ // TODO: Check key/value types here?
+ "foreach (" ~ i ~ "; 0 .. " ~ map ~ ".size) {\n" ~
+ "FullyUnqual!(typeof(" ~ v ~ ".keys[0])) " ~ key ~ ";\n" ~
+ readValueCode!K(key, level + 1) ~ "\n" ~
+ "typeof(" ~ v ~ ".values[0]) " ~ value ~ ";\n" ~
+ readValueCode!V(value, level + 1) ~ "\n" ~
+ v ~ "[cast(typeof(" ~ v ~ ".keys[0]))" ~ key ~ "] = " ~ value ~ ";\n" ~
+ "}\n" ~
+ "p.readMapEnd();" ~
+ "}";
+ } else static if (is(F _ : HashSet!(E), E)) {
+ return "{\n" ~
+ "auto " ~ set ~ " = p.readSetBegin();" ~
+ // TODO: Check element type here?
+ v ~ " = new typeof(" ~ v ~ ")();\n" ~
+ "foreach (" ~ i ~ "; 0 .. " ~ set ~ ".size) {\n" ~
+ "typeof(" ~ v ~ "[][0]) " ~ elem ~ ";\n" ~
+ readValueCode!E(elem, level + 1) ~ "\n" ~
+ v ~ " ~= " ~ elem ~ ";\n" ~
+ "}\n" ~
+ "p.readSetEnd();" ~
+ "}";
+ } else static if (is(F == struct) || is(F : TException)) {
+ static if (is(F == struct)) {
+ auto result = v ~ " = typeof(" ~ v ~ ")();\n";
+ } else {
+ auto result = v ~ " = new typeof(" ~ v ~ ")();\n";
+ }
+
+ static if (__traits(compiles, F.init.read(TProtocol.init))) {
+ result ~= v ~ ".read(p);";
+ } else {
+ result ~= "readStruct(" ~ v ~ ", p);";
+ }
+ return result;
+ } else {
+ static assert(false, "Cannot represent type in Thrift: " ~ F.stringof);
+ }
+ }
+
+ string readFieldCode(FieldType)(string name, short id, TReq req) {
+ static if (pointerStruct && isPointer!FieldType) {
+ immutable v = "(*s." ~ name ~ ")";
+ alias pointerTarget!FieldType F;
+ } else {
+ immutable v = "s." ~ name;
+ alias FieldType F;
+ }
+
+ string code = "case " ~ to!string(id) ~ ":\n";
+ code ~= "if (f.type == " ~ dToTTypeString!F ~ ") {\n";
+ code ~= readValueCode!F(v) ~ "\n";
+ if (req == TReq.REQUIRED) {
+ // For required fields, set the corresponding local isSet variable.
+ code ~= "isSet_" ~ name ~ " = true;\n";
+ } else if (!isNullable!F){
+ code ~= "s.isSetFlags." ~ name ~ " = true;\n";
+ }
+ code ~= "} else skip(p, f.type);\n";
+ code ~= "break;\n";
+ return code;
+ }
+
+ // Code for the local boolean flags used to make sure required fields have
+ // been found.
+ string isSetFlagCode = "";
+
+ // Code for checking whether the flags for the required fields are true.
+ string isSetCheckCode = "";
+
+ /// Code for the case statements storing the fields to the result struct.
+ string readMembersCode = "";
+
+ // The last automatically assigned id â fields with no meta information
+ // are assigned (in lexical order) descending negative ids, starting with
+ // -1, just like the Thrift compiler does.
+ short lastId;
+
+ foreach (name; FieldNames!T) {
+ enum req = memberReq!(T, name, fieldMetaData);
+ if (req == TReq.REQUIRED) {
+ // For required fields, generate local bool flags to keep track
+ // whether the field has been encountered.
+ immutable n = "isSet_" ~ name;
+ isSetFlagCode ~= "bool " ~ n ~ ";\n";
+ isSetCheckCode ~= "enforce(" ~ n ~ ", new TProtocolException(" ~
+ "`Required field '" ~ name ~ "' not found in serialized data`, " ~
+ "TProtocolException.Type.INVALID_DATA));\n";
+ }
+
+ enum meta = find!`a.name == b`(mergeFieldMeta!(T, fieldMetaData), name);
+ static if (meta.empty) {
+ --lastId;
+ version (TVerboseCodegen) {
+ code ~= "pragma(msg, `[thrift.codegen.base.readStruct] Warning: No " ~
+ "meta information for field '" ~ name ~ "' in struct '" ~
+ T.stringof ~ "'. Assigned id: " ~ to!string(lastId) ~ ".`);\n";
+ }
+ readMembersCode ~= readFieldCode!(MemberType!(T, name))(
+ name, lastId, req);
+ } else static if (req != TReq.IGNORE) {
+ readMembersCode ~= readFieldCode!(MemberType!(T, name))(
+ name, meta.front.id, req);
+ }
+ }
+
+ code ~= isSetFlagCode;
+ code ~= "p.readStructBegin();\n";
+ code ~= "while (true) {\n";
+ code ~= "auto f = p.readFieldBegin();\n";
+ code ~= "if (f.type == TType.STOP) break;\n";
+ code ~= "switch(f.id) {\n";
+ code ~= readMembersCode;
+ code ~= "default: skip(p, f.type);\n";
+ code ~= "}\n";
+ code ~= "p.readFieldEnd();\n";
+ code ~= "}\n";
+ code ~= "p.readStructEnd();\n";
+ code ~= isSetCheckCode;
+
+ return code;
+ }());
+}
+
+/**
+ * Serializes a struct to the target protocol.
+ *
+ * Using the Protocol template parameter, the concrete TProtocol to use can be
+ * be specified. If the pointerStruct parameter is set to true, the struct
+ * fields are expected to be pointers to the actual data. This is used
+ * internally (combined with TPargsStruct) and usually should not be used in
+ * user code.
+ *
+ * This is a free function to make it possible to read exisiting structs from
+ * the wire without altering their definitions.
+ */
+void writeStruct(T, Protocol, alias fieldMetaData = cast(TFieldMeta[])null,
+ bool pointerStruct = false) (const T s, Protocol p) if (isTProtocol!Protocol)
+{
+ mixin({
+ // Check that all fields for which there is meta info are actually in the
+ // passed struct type.
+ string code = "";
+ foreach (field; mergeFieldMeta!(T, fieldMetaData)) {
+ code ~= "static assert(is(MemberType!(T, `" ~ field.name ~ "`)));\n";
+ }
+
+ // Check that required nullable members are non-null.
+ // WORKAROUND: To stop LDC from emitting the manifest constant »meta« below
+ // into the writeStruct function body this is inside the string mixin
+ // block â the code wouldn't depend on it (this is an LDC bug, and because
+ // of it a new array would be allocate on each method invocation at runtime).
+ foreach (name; StaticFilter!(
+ Compose!(isNullable, PApply!(MemberType, T)),
+ FieldNames!T
+ )) {
+ static if (memberReq!(T, name, fieldMetaData) == TReq.REQUIRED) {
+ code ~= `enforce(__traits(getMember, s, name) !is null,
+ new TException("Required field '` ~ name ~ `' is null."));\n`;
+ }
+ }
+
+ return code;
+ }());
+
+ p.writeStructBegin(TStruct(T.stringof));
+ mixin({
+ string writeValueCode(ValueType)(string v, size_t level = 0) {
+ // Some non-ambigous names to use (shadowing is not allowed in D).
+ immutable elem = "elem" ~ to!string(level);
+ immutable key = "key" ~ to!string(level);
+ immutable value = "value" ~ to!string(level);
+
+ alias FullyUnqual!ValueType F;
+ static if (is(F == bool)) {
+ return "p.writeBool(" ~ v ~ ");";
+ } else static if (is(F == byte)) {
+ return "p.writeByte(" ~ v ~ ");";
+ } else static if (is(F == double)) {
+ return "p.writeDouble(" ~ v ~ ");";
+ } else static if (is(F == short)) {
+ return "p.writeI16(" ~ v ~ ");";
+ } else static if (is(F == int)) {
+ return "p.writeI32(" ~ v ~ ");";
+ } else static if (is(F == long)) {
+ return "p.writeI64(" ~ v ~ ");";
+ } else static if (is(F : string)) {
+ return "p.writeString(" ~ v ~ ");";
+ } else static if (is(F == enum)) {
+ return "p.writeI32(cast(int)" ~ v ~ ");";
+ } else static if (is(F _ : E[], E)) {
+ return "p.writeListBegin(TList(" ~ dToTTypeString!E ~ ", " ~ v ~
+ ".length));\n" ~
+ "foreach (" ~ elem ~ "; " ~ v ~ ") {\n" ~
+ writeValueCode!E(elem, level + 1) ~ "\n" ~
+ "}\n" ~
+ "p.writeListEnd();";
+ } else static if (is(F _ : V[K], K, V)) {
+ return "p.writeMapBegin(TMap(" ~ dToTTypeString!K ~ ", " ~
+ dToTTypeString!V ~ ", " ~ v ~ ".length));\n" ~
+ "foreach (" ~ key ~ ", " ~ value ~ "; " ~ v ~ ") {\n" ~
+ writeValueCode!K(key, level + 1) ~ "\n" ~
+ writeValueCode!V(value, level + 1) ~ "\n" ~
+ "}\n" ~
+ "p.writeMapEnd();";
+ } else static if (is(F _ : HashSet!E, E)) {
+ return "p.writeSetBegin(TSet(" ~ dToTTypeString!E ~ ", " ~ v ~
+ ".length));\n" ~
+ "foreach (" ~ elem ~ "; " ~ v ~ "[]) {\n" ~
+ writeValueCode!E(elem, level + 1) ~ "\n" ~
+ "}\n" ~
+ "p.writeSetEnd();";
+ } else static if (is(F == struct) || is(F : TException)) {
+ static if (__traits(compiles, F.init.write(TProtocol.init))) {
+ return v ~ ".write(p);";
+ } else {
+ return "writeStruct(" ~ v ~ ", p);";
+ }
+ } else {
+ static assert(false, "Cannot represent type in Thrift: " ~ F.stringof);
+ }
+ }
+
+ string writeFieldCode(FieldType)(string name, short id, TReq req) {
+ string code;
+ if (!pointerStruct && req == TReq.OPTIONAL) {
+ code ~= "if (s.isSet!`" ~ name ~ "`) {\n";
+ }
+
+ static if (pointerStruct && isPointer!FieldType) {
+ immutable v = "(*s." ~ name ~ ")";
+ alias pointerTarget!FieldType F;
+ } else {
+ immutable v = "s." ~ name;
+ alias FieldType F;
+ }
+
+ code ~= "p.writeFieldBegin(TField(`" ~ name ~ "`, " ~ dToTTypeString!F ~
+ ", " ~ to!string(id) ~ "));\n";
+ code ~= writeValueCode!F(v) ~ "\n";
+ code ~= "p.writeFieldEnd();\n";
+
+ if (!pointerStruct && req == TReq.OPTIONAL) {
+ code ~= "}\n";
+ }
+ return code;
+ }
+
+ // The last automatically assigned id â fields with no meta information
+ // are assigned (in lexical order) descending negative ids, starting with
+ // -1, just like the Thrift compiler does.
+ short lastId;
+
+ string code = "";
+ foreach (name; FieldNames!T) {
+ alias MemberType!(T, name) F;
+ enum req = memberReq!(T, name, fieldMetaData);
+ enum meta = find!`a.name == b`(mergeFieldMeta!(T, fieldMetaData), name);
+ if (meta.empty) {
+ --lastId;
+ version (TVerboseCodegen) {
+ code ~= "pragma(msg, `[thrift.codegen.base.writeStruct] Warning: No " ~
+ "meta information for field '" ~ name ~ "' in struct '" ~
+ T.stringof ~ "'. Assigned id: " ~ to!string(lastId) ~ ".`);\n";
+ }
+ code ~= writeFieldCode!F(name, lastId, req);
+ } else if (req != TReq.IGNORE) {
+ code ~= writeFieldCode!F(name, meta.front.id, req);
+ }
+ }
+
+ return code;
+ }());
+ p.writeFieldStop();
+ p.writeStructEnd();
+}
+
+private {
+ /*
+ * Returns a D code string containing the matching TType value for a passed
+ * D type, e.g. dToTTypeString!byte == "TType.BYTE".
+ */
+ template dToTTypeString(T) {
+ static if (is(FullyUnqual!T == bool)) {
+ enum dToTTypeString = "TType.BOOL";
+ } else static if (is(FullyUnqual!T == byte)) {
+ enum dToTTypeString = "TType.BYTE";
+ } else static if (is(FullyUnqual!T == double)) {
+ enum dToTTypeString = "TType.DOUBLE";
+ } else static if (is(FullyUnqual!T == short)) {
+ enum dToTTypeString = "TType.I16";
+ } else static if (is(FullyUnqual!T == int)) {
+ enum dToTTypeString = "TType.I32";
+ } else static if (is(FullyUnqual!T == long)) {
+ enum dToTTypeString = "TType.I64";
+ } else static if (is(FullyUnqual!T : string)) {
+ enum dToTTypeString = "TType.STRING";
+ } else static if (is(FullyUnqual!T == enum)) {
+ enum dToTTypeString = "TType.I32";
+ } else static if (is(FullyUnqual!T _ : U[], U)) {
+ enum dToTTypeString = "TType.LIST";
+ } else static if (is(FullyUnqual!T _ : V[K], K, V)) {
+ enum dToTTypeString = "TType.MAP";
+ } else static if (is(FullyUnqual!T _ : HashSet!E, E)) {
+ enum dToTTypeString = "TType.SET";
+ } else static if (is(FullyUnqual!T == struct)) {
+ enum dToTTypeString = "TType.STRUCT";
+ } else static if (is(FullyUnqual!T : TException)) {
+ enum dToTTypeString = "TType.STRUCT";
+ } else {
+ static assert(false, "Cannot represent type in Thrift: " ~ T.stringof);
+ }
+ }
+}
Added: thrift/trunk/lib/d/src/thrift/codegen/client.d
URL: http://svn.apache.org/viewvc/thrift/trunk/lib/d/src/thrift/codegen/client.d?rev=1304085&view=auto
==============================================================================
--- thrift/trunk/lib/d/src/thrift/codegen/client.d (added)
+++ thrift/trunk/lib/d/src/thrift/codegen/client.d Thu Mar 22 21:49:10 2012
@@ -0,0 +1,484 @@
+/*
+ * 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 ~
+ "`, TMessageType.CALL, ++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 recieving 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;
+ }());
+}
Added: thrift/trunk/lib/d/src/thrift/codegen/client_pool.d
URL: http://svn.apache.org/viewvc/thrift/trunk/lib/d/src/thrift/codegen/client_pool.d?rev=1304085&view=auto
==============================================================================
--- thrift/trunk/lib/d/src/thrift/codegen/client_pool.d (added)
+++ thrift/trunk/lib/d/src/thrift/codegen/client_pool.d Thu Mar 22 21:49:10 2012
@@ -0,0 +1,262 @@
+/*
+ * 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_pool;
+
+import core.time : dur, Duration, TickDuration;
+import std.traits : ParameterTypeTuple, ReturnType;
+import thrift.base;
+import thrift.codegen.base;
+import thrift.codegen.client;
+import thrift.internal.codegen;
+import thrift.internal.resource_pool;
+
+/**
+ * Manages a pool of TClients for the given interface, forwarding RPC calls to
+ * members of the pool.
+ *
+ * If a request fails, another client from the pool is tried, and optionally,
+ * a client is disabled for a configurable amount of time if it fails too
+ * often. If all clients fail (and keepTrying is false), a
+ * TCompoundOperationException is thrown, containing all the collected RPC
+ * exceptions.
+ */
+class TClientPool(Interface) if (isService!Interface) : Interface {
+ /// Shorthand for TClientBase!Interface, the client type this instance
+ /// operates on.
+ alias TClientBase!Interface Client;
+
+ /**
+ * Creates a new instance and adds the given clients to the pool.
+ */
+ this(Client[] clients) {
+ pool_ = new TResourcePool!Client(clients);
+
+ rpcFaultFilter = (Exception e) {
+ import thrift.protocol.base;
+ import thrift.transport.base;
+ return (
+ (cast(TTransportException)e !is null) ||
+ (cast(TApplicationException)e !is null)
+ );
+ };
+ }
+
+ /**
+ * Executes an operation on the first currently active client.
+ *
+ * If the operation fails (throws an exception for which rpcFaultFilter is
+ * true), the failure is recorded and the next client in the pool is tried.
+ *
+ * Throws: Any non-rpc exception that occurs, a TCompoundOperationException
+ * if all clients failed with an rpc exception (if keepTrying is false).
+ *
+ * Example:
+ * ---
+ * interface Foo { string bar(); }
+ * auto poolClient = tClientPool([tClient!Foo(someProtocol)]);
+ * auto result = poolClient.execute((c){ return c.bar(); });
+ * ---
+ */
+ ResultType execute(ResultType)(scope ResultType delegate(Client) work) {
+ return executeOnPool!Client(work);
+ }
+
+ /**
+ * Adds a client to the pool.
+ */
+ void addClient(Client client) {
+ pool_.add(client);
+ }
+
+ /**
+ * Removes a client from the pool.
+ *
+ * Returns: Whether the client was found in the pool.
+ */
+ bool removeClient(Client client) {
+ return pool_.remove(client);
+ }
+
+ mixin(poolForwardCode!Interface());
+
+ /// Whether to open the underlying transports of a client before trying to
+ /// execute a method if they are not open. This is usually desirable
+ /// because it allows e.g. to automatically reconnect to a remote server
+ /// if the network connection is dropped.
+ ///
+ /// Defaults to true.
+ bool reopenTransports = true;
+
+ /// Called to determine whether an exception comes from a client from the
+ /// pool not working properly, or if it an exception thrown at the
+ /// application level.
+ ///
+ /// If the delegate returns true, the server/connection is considered to be
+ /// at fault, if it returns false, the exception is just passed on to the
+ /// caller.
+ ///
+ /// By default, returns true for instances of TTransportException and
+ /// TApplicationException, false otherwise.
+ bool delegate(Exception) rpcFaultFilter;
+
+ /**
+ * Whether to keep trying to find a working client if all have failed in a
+ * row.
+ *
+ * Defaults to false.
+ */
+ bool keepTrying() const @property {
+ return pool_.cycle;
+ }
+
+ /// Ditto
+ void keepTrying(bool value) @property {
+ pool_.cycle = value;
+ }
+
+ /**
+ * Whether to use a random permutation of the client pool on every call to
+ * execute(). This can be used e.g. as a simple form of load balancing.
+ *
+ * Defaults to true.
+ */
+ bool permuteClients() const @property {
+ return pool_.permute;
+ }
+
+ /// Ditto
+ void permuteClients(bool value) @property {
+ pool_.permute = value;
+ }
+
+ /**
+ * The number of consecutive faults after which a client is disabled until
+ * faultDisableDuration has passed. 0 to never disable clients.
+ *
+ * Defaults to 0.
+ */
+ ushort faultDisableCount() @property {
+ return pool_.faultDisableCount;
+ }
+
+ /// Ditto
+ void faultDisableCount(ushort value) @property {
+ pool_.faultDisableCount = value;
+ }
+
+ /**
+ * The duration for which a client is no longer considered after it has
+ * failed too often.
+ *
+ * Defaults to one second.
+ */
+ Duration faultDisableDuration() @property {
+ return pool_.faultDisableDuration;
+ }
+
+ /// Ditto
+ void faultDisableDuration(Duration value) @property {
+ pool_.faultDisableDuration = value;
+ }
+
+protected:
+ ResultType executeOnPool(ResultType)(scope ResultType delegate(Client) work) {
+ auto clients = pool_[];
+ if (clients.empty) {
+ throw new TException("No clients available to try.");
+ }
+
+ while (true) {
+ Exception[] rpcExceptions;
+ while (!clients.empty) {
+ auto c = clients.front;
+ clients.popFront;
+ try {
+ scope (success) {
+ pool_.recordSuccess(c);
+ }
+
+ if (reopenTransports) {
+ c.inputProtocol.transport.open();
+ c.outputProtocol.transport.open();
+ }
+
+ return work(c);
+ } catch (Exception e) {
+ if (rpcFaultFilter && rpcFaultFilter(e)) {
+ pool_.recordFault(c);
+ rpcExceptions ~= e;
+ } else {
+ // We are dealing with a normal exception thrown by the
+ // server-side method, just pass it on. As far as we are
+ // concerned, the method call succeded.
+ pool_.recordSuccess(c);
+ throw e;
+ }
+ }
+ }
+
+ // If we get here, no client succeeded during the current iteration.
+ Duration waitTime;
+ Client dummy;
+ if (clients.willBecomeNonempty(dummy, waitTime)) {
+ if (waitTime > dur!"hnsecs"(0)) {
+ import core.thread;
+ Thread.sleep(waitTime);
+ }
+ } else {
+ throw new TCompoundOperationException("All clients failed.",
+ rpcExceptions);
+ }
+ }
+ }
+
+private:
+ TResourcePool!Client pool_;
+}
+
+private {
+ // Cannot use an anonymous delegate literal for this because they aren't
+ // allowed in class scope.
+ string poolForwardCode(Interface)() {
+ string code = "";
+
+ foreach (methodName; AllMemberMethodNames!Interface) {
+ enum qn = "Interface." ~ methodName;
+ code ~= "ReturnType!(" ~ qn ~ ") " ~ methodName ~
+ "(ParameterTypeTuple!(" ~ qn ~ ") args) {\n";
+ code ~= "return executeOnPool((Client c){ return c." ~
+ methodName ~ "(args); });\n";
+ code ~= "}\n";
+ }
+
+ return code;
+ }
+}
+
+/**
+ * TClientPool construction helper to avoid having to explicitly specify
+ * the interface type, i.e. to allow the constructor being called using IFTI
+ * (see $(DMDBUG 6082, D Bugzilla enhancement requet 6082)).
+ */
+TClientPool!Interface tClientPool(Interface)(
+ TClientBase!Interface[] clients
+) if (isService!Interface) {
+ return new typeof(return)(clients);
+}
Added: thrift/trunk/lib/d/src/thrift/codegen/idlgen.d
URL: http://svn.apache.org/viewvc/thrift/trunk/lib/d/src/thrift/codegen/idlgen.d?rev=1304085&view=auto
==============================================================================
--- thrift/trunk/lib/d/src/thrift/codegen/idlgen.d (added)
+++ thrift/trunk/lib/d/src/thrift/codegen/idlgen.d Thu Mar 22 21:49:10 2012
@@ -0,0 +1,767 @@
+/*
+ * 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.
+ */
+
+/**
+ * Contains <b>experimental</b> functionality for generating Thrift IDL files
+ * (.thrift) from existing D data structures, i.e. the reverse of what the
+ * Thrift compiler does.
+ */
+module thrift.codegen.idlgen;
+
+import std.algorithm : find;
+import std.array : empty, front;
+import std.conv : to;
+import std.traits : EnumMembers, isSomeFunction, OriginalType,
+ ParameterTypeTuple, ReturnType;
+import std.typetuple : allSatisfy, staticIndexOf, staticMap, NoDuplicates,
+ TypeTuple;
+import thrift.base;
+import thrift.codegen.base;
+import thrift.internal.codegen;
+import thrift.internal.ctfe;
+import thrift.util.hashset;
+
+/**
+ * True if the passed type is a Thrift entity (struct, exception, enum,
+ * service).
+ */
+alias Any!(isStruct, isException, isEnum, isService) isThriftEntity;
+
+/**
+ * Returns an IDL string describing the passed »root« entities and all types
+ * they depend on.
+ */
+template idlString(Roots...) if (allSatisfy!(isThriftEntity, Roots)) {
+ enum idlString = idlStringImpl!Roots.result;
+}
+
+private {
+ template idlStringImpl(Roots...) if (allSatisfy!(isThriftEntity, Roots)) {
+ alias ForAllWithList!(
+ ConfinedTuple!(StaticFilter!(isService, Roots)),
+ AddBaseServices
+ ) Services;
+
+ alias TypeTuple!(
+ StaticFilter!(isEnum, Roots),
+ ForAllWithList!(
+ ConfinedTuple!(
+ StaticFilter!(Any!(isException, isStruct), Roots),
+ staticMap!(CompositeTypeDeps, staticMap!(ServiceTypeDeps, Services))
+ ),
+ AddStructWithDeps
+ )
+ ) Types;
+
+ enum result = ctfeJoin(
+ [
+ staticMap!(
+ enumIdlString,
+ StaticFilter!(isEnum, Types)
+ ),
+ staticMap!(
+ structIdlString,
+ StaticFilter!(Any!(isStruct, isException), Types)
+ ),
+ staticMap!(
+ serviceIdlString,
+ Services
+ )
+ ],
+ "\n"
+ );
+ }
+
+ template ServiceTypeDeps(T) if (isService!T) {
+ alias staticMap!(
+ PApply!(MethodTypeDeps, T),
+ FilterMethodNames!(T, __traits(derivedMembers, T))
+ ) ServiceTypeDeps;
+ }
+
+ template MethodTypeDeps(T, string name) if (
+ isService!T && isSomeFunction!(MemberType!(T, name))
+ ) {
+ alias TypeTuple!(
+ ReturnType!(MemberType!(T, name)),
+ ParameterTypeTuple!(MemberType!(T, name)),
+ ExceptionTypes!(T, name)
+ ) MethodTypeDeps;
+ }
+
+ template ExceptionTypes(T, string name) if (
+ isService!T && isSomeFunction!(MemberType!(T, name))
+ ) {
+ mixin({
+ enum meta = find!`a.name == b`(getMethodMeta!T, name);
+ if (meta.empty) return "alias TypeTuple!() ExceptionTypes;";
+
+ string result = "alias TypeTuple!(";
+ foreach (i, e; meta.front.exceptions) {
+ if (i > 0) result ~= ", ";
+ result ~= "mixin(`T." ~ e.type ~ "`)";
+ }
+ result ~= ") ExceptionTypes;";
+ return result;
+ }());
+ }
+
+ template AddBaseServices(T, List...) {
+ static if (staticIndexOf!(T, List) == -1) {
+ alias NoDuplicates!(BaseServices!T, List) AddBaseServices;
+ } else {
+ alias List AddStructWithDeps;
+ }
+ }
+
+ unittest {
+ interface A {}
+ interface B : A {}
+ interface C : B {}
+ interface D : A {}
+
+ static assert(is(AddBaseServices!(C) == TypeTuple!(A, B, C)));
+ static assert(is(ForAllWithList!(ConfinedTuple!(C, D), AddBaseServices) ==
+ TypeTuple!(A, D, B, C)));
+ }
+
+ template BaseServices(T, Rest...) if (isService!T) {
+ static if (isDerivedService!T) {
+ alias BaseServices!(BaseService!T, T, Rest) BaseServices;
+ } else {
+ alias TypeTuple!(T, Rest) BaseServices;
+ }
+ }
+
+ template AddStructWithDeps(T, List...) {
+ static if (staticIndexOf!(T, List) == -1) {
+ // T is not already in the List, so add T and the types it depends on in
+ // the front. Because with the Thrift compiler types can only depend on
+ // other types that have already been defined, we collect all the
+ // dependencies, prepend them to the list, and then prune the duplicates
+ // (keeping the first occurences). If this requirement should ever be
+ // dropped from Thrift, this could be easily adapted to handle circular
+ // dependencies by passing TypeTuple!(T, List) to ForAllWithList instead
+ // of appending List afterwards, and removing the now unneccesary
+ // NoDuplicates.
+ alias NoDuplicates!(
+ ForAllWithList!(
+ ConfinedTuple!(
+ staticMap!(
+ CompositeTypeDeps,
+ staticMap!(
+ PApply!(MemberType, T),
+ FieldNames!T
+ )
+ )
+ ),
+ .AddStructWithDeps,
+ T
+ ),
+ List
+ ) AddStructWithDeps;
+ } else {
+ alias List AddStructWithDeps;
+ }
+ }
+
+ version (unittest) {
+ struct A {}
+ struct B {
+ A a;
+ int b;
+ A c;
+ string d;
+ }
+ struct C {
+ B b;
+ A a;
+ }
+
+ static assert(is(AddStructWithDeps!C == TypeTuple!(A, B, C)));
+
+ struct D {
+ C c;
+ mixin TStructHelpers!([TFieldMeta("c", 0, TReq.IGNORE)]);
+ }
+ static assert(is(AddStructWithDeps!D == TypeTuple!(D)));
+ }
+
+ version (unittest) {
+ // Circles in the type dependency graph are not allowed in Thrift, but make
+ // sure we fail in a sane way instead of crashing the compiler.
+
+ struct Rec1 {
+ Rec2[] other;
+ }
+
+ struct Rec2 {
+ Rec1[] other;
+ }
+
+ static assert(!__traits(compiles, AddStructWithDeps!Rec1));
+ }
+
+ /*
+ * Returns the non-primitive types T directly depends on.
+ *
+ * For example, CompositeTypeDeps!int would yield an empty type tuple,
+ * CompositeTypeDeps!SomeStruct would give SomeStruct, and
+ * CompositeTypeDeps!(A[B]) both CompositeTypeDeps!A and CompositeTypeDeps!B.
+ */
+ template CompositeTypeDeps(T) {
+ static if (is(FullyUnqual!T == bool) || is(FullyUnqual!T == byte) ||
+ is(FullyUnqual!T == short) || is(FullyUnqual!T == int) ||
+ is(FullyUnqual!T == long) || is(FullyUnqual!T : string) ||
+ is(FullyUnqual!T == double) || is(FullyUnqual!T == void)
+ ) {
+ alias TypeTuple!() CompositeTypeDeps;
+ } else static if (is(FullyUnqual!T _ : U[], U)) {
+ alias CompositeTypeDeps!U CompositeTypeDeps;
+ } else static if (is(FullyUnqual!T _ : HashSet!E, E)) {
+ alias CompositeTypeDeps!E CompositeTypeDeps;
+ } else static if (is(FullyUnqual!T _ : V[K], K, V)) {
+ alias TypeTuple!(CompositeTypeDeps!K, CompositeTypeDeps!V) CompositeTypeDeps;
+ } else static if (is(FullyUnqual!T == enum) || is(FullyUnqual!T == struct) ||
+ is(FullyUnqual!T : TException)
+ ) {
+ alias TypeTuple!(FullyUnqual!T) CompositeTypeDeps;
+ } else {
+ static assert(false, "Cannot represent type in Thrift: " ~ T.stringof);
+ }
+ }
+}
+
+/**
+ * Returns an IDL string describing the passed service. IDL code for any type
+ * dependcies is not included.
+ */
+template serviceIdlString(T) if (isService!T) {
+ enum serviceIdlString = {
+ string result = "service " ~ T.stringof;
+ static if (isDerivedService!T) {
+ result ~= " extends " ~ BaseService!T.stringof;
+ }
+ result ~= " {\n";
+
+ foreach (methodName; FilterMethodNames!(T, __traits(derivedMembers, T))) {
+ result ~= " ";
+
+ enum meta = find!`a.name == b`(T.methodMeta, methodName);
+
+ static if (!meta.empty && meta.front.type == TMethodType.ONEWAY) {
+ result ~= "oneway ";
+ }
+
+ alias ReturnType!(MemberType!(T, methodName)) RT;
+ static if (is(RT == void)) {
+ // We special-case this here instead of adding void to dToIdlType to
+ // avoid accepting things like void[].
+ result ~= "void ";
+ } else {
+ result ~= dToIdlType!RT ~ " ";
+ }
+ result ~= methodName ~ "(";
+
+ short lastId;
+ foreach (i, ParamType; ParameterTypeTuple!(MemberType!(T, methodName))) {
+ static if (!meta.empty && i < meta.front.params.length) {
+ enum havePM = true;
+ } else {
+ enum havePM = false;
+ }
+
+ short id;
+ static if (havePM) {
+ id = meta.front.params[i].id;
+ } else {
+ id = --lastId;
+ }
+
+ string paramName;
+ static if (havePM) {
+ paramName = meta.front.params[i].name;
+ } else {
+ paramName = "param" ~ to!string(i + 1);
+ }
+
+ result ~= to!string(id) ~ ": " ~ dToIdlType!ParamType ~ " " ~ paramName;
+
+ static if (havePM && !meta.front.params[i].defaultValue.empty) {
+ result ~= " = " ~ dToIdlConst(mixin(meta.front.params[i].defaultValue));
+ } else {
+ // Unfortunately, getting the default value for parameters from a
+ // function alias isn't possible â we can't transfer the default
+ // value to the IDL e.g. for interface Foo { void foo(int a = 5); }
+ // without the user explicitly declaring it in metadata.
+ }
+ result ~= ", ";
+ }
+ result ~= ")";
+
+ static if (!meta.empty && !meta.front.exceptions.empty) {
+ result ~= " throws (";
+ foreach (e; meta.front.exceptions) {
+ result ~= to!string(e.id) ~ ": " ~ e.type ~ " " ~ e.name ~ ", ";
+ }
+ result ~= ")";
+ }
+
+ result ~= ",\n";
+ }
+
+ result ~= "}\n";
+ return result;
+ }();
+}
+
+/**
+ * Returns an IDL string describing the passed enum. IDL code for any type
+ * dependcies is not included.
+ */
+template enumIdlString(T) if (isEnum!T) {
+ enum enumIdlString = {
+ static assert(is(OriginalType!T : long),
+ "Can only have integer enums in Thrift (not " ~ OriginalType!T.stringof ~
+ ", for " ~ T.stringof ~ ").");
+
+ string result = "enum " ~ T.stringof ~ " {\n";
+
+ foreach (name; __traits(derivedMembers, T)) {
+ result ~= " " ~ name ~ " = " ~ dToIdlConst(GetMember!(T, name)) ~ ",\n";
+ }
+
+ result ~= "}\n";
+ return result;
+ }();
+}
+
+/**
+ * Returns an IDL string describing the passed struct. IDL code for any type
+ * dependcies is not included.
+ */
+template structIdlString(T) if (isStruct!T || isException!T) {
+ enum structIdlString = {
+ mixin({
+ string code = "";
+ foreach (field; getFieldMeta!T) {
+ code ~= "static assert(is(MemberType!(T, `" ~ field.name ~ "`)));\n";
+ }
+ return code;
+ }());
+
+ string result;
+ static if (isException!T) {
+ result = "exception ";
+ } else {
+ result = "struct ";
+ }
+ result ~= T.stringof ~ " {\n";
+
+ // The last automatically assigned id â fields with no meta information
+ // are assigned (in lexical order) descending negative ids, starting with
+ // -1, just like the Thrift compiler does.
+ short lastId;
+
+ foreach (name; FieldNames!T) {
+ enum meta = find!`a.name == b`(getFieldMeta!T, name);
+
+ static if (meta.empty || meta.front.req != TReq.IGNORE) {
+ short id;
+ static if (meta.empty) {
+ id = --lastId;
+ } else {
+ id = meta.front.id;
+ }
+
+ result ~= " " ~ to!string(id) ~ ":";
+ static if (!meta.empty) {
+ result ~= dToIdlReq(meta.front.req);
+ }
+ result ~= " " ~ dToIdlType!(MemberType!(T, name)) ~ " " ~ name;
+
+ static if (!meta.empty && !meta.front.defaultValue.empty) {
+ result ~= " = " ~ dToIdlConst(mixin(meta.front.defaultValue));
+ } else static if (__traits(compiles, fieldInitA!(T, name))) {
+ static if (is(typeof(fieldInitA!(T, name))) &&
+ !is(typeof(fieldInitA!(T, name)) == void)
+ ) {
+ result ~= " = " ~ dToIdlConst(fieldInitA!(T, name));
+ }
+ } else static if (is(typeof(fieldInitB!(T, name))) &&
+ !is(typeof(fieldInitB!(T, name)) == void)
+ ) {
+ result ~= " = " ~ dToIdlConst(fieldInitB!(T, name));
+ }
+ result ~= ",\n";
+ }
+ }
+
+ result ~= "}\n";
+ return result;
+ }();
+}
+
+private {
+ // This very convoluted way of doing things was chosen because putting the
+ // static if directly into structIdlString caused »not evaluatable at compile
+ // time« errors to slip through even though typeof() was used, resp. the
+ // condition to be true even though the value couldn't actually be read at
+ // compile time due to a @@BUG@@ in DMD 2.055.
+ // The extra »compiled« field in fieldInitA is needed because we must not try
+ // to use != if !is compiled as well (but was false), e.g. for floating point
+ // types.
+ template fieldInitA(T, string name) {
+ static if (mixin("T.init." ~ name) !is MemberType!(T, name).init) {
+ enum fieldInitA = mixin("T.init." ~ name);
+ }
+ }
+
+ template fieldInitB(T, string name) {
+ static if (mixin("T.init." ~ name) != MemberType!(T, name).init) {
+ enum fieldInitB = mixin("T.init." ~ name);
+ }
+ }
+
+ template dToIdlType(T) {
+ static if (is(FullyUnqual!T == bool)) {
+ enum dToIdlType = "bool";
+ } else static if (is(FullyUnqual!T == byte)) {
+ enum dToIdlType = "byte";
+ } else static if (is(FullyUnqual!T == double)) {
+ enum dToIdlType = "double";
+ } else static if (is(FullyUnqual!T == short)) {
+ enum dToIdlType = "i16";
+ } else static if (is(FullyUnqual!T == int)) {
+ enum dToIdlType = "i32";
+ } else static if (is(FullyUnqual!T == long)) {
+ enum dToIdlType = "i64";
+ } else static if (is(FullyUnqual!T : string)) {
+ enum dToIdlType = "string";
+ } else static if (is(FullyUnqual!T _ : U[], U)) {
+ enum dToIdlType = "list<" ~ dToIdlType!U ~ ">";
+ } else static if (is(FullyUnqual!T _ : V[K], K, V)) {
+ enum dToIdlType = "map<" ~ dToIdlType!K ~ ", " ~ dToIdlType!V ~ ">";
+ } else static if (is(FullyUnqual!T _ : HashSet!E, E)) {
+ enum dToIdlType = "set<" ~ dToIdlType!E ~ ">";
+ } else static if (is(FullyUnqual!T == struct) || is(FullyUnqual!T == enum) ||
+ is(FullyUnqual!T : TException)
+ ) {
+ enum dToIdlType = FullyUnqual!(T).stringof;
+ } else {
+ static assert(false, "Cannot represent type in Thrift: " ~ T.stringof);
+ }
+ }
+
+ string dToIdlReq(TReq req) {
+ switch (req) {
+ case TReq.REQUIRED: return " required";
+ case TReq.OPTIONAL: return " optional";
+ default: return "";
+ }
+ }
+
+ string dToIdlConst(T)(T value) {
+ static if (is(FullyUnqual!T == bool)) {
+ return value ? "1" : "0";
+ } else static if (is(FullyUnqual!T == byte) ||
+ is(FullyUnqual!T == short) || is(FullyUnqual!T == int) ||
+ is(FullyUnqual!T == long)
+ ) {
+ return to!string(value);
+ } else static if (is(FullyUnqual!T : string)) {
+ return `"` ~ to!string(value) ~ `"`;
+ } else static if (is(FullyUnqual!T == double)) {
+ return ctfeToString(value);
+ } else static if (is(FullyUnqual!T _ : U[], U) ||
+ is(FullyUnqual!T _ : HashSet!E, E)
+ ) {
+ string result = "[";
+ foreach (e; value) {
+ result ~= dToIdlConst(e) ~ ", ";
+ }
+ result ~= "]";
+ return result;
+ } else static if (is(FullyUnqual!T _ : V[K], K, V)) {
+ string result = "{";
+ foreach (key, val; value) {
+ result ~= dToIdlConst(key) ~ ": " ~ dToIdlConst(val) ~ ", ";
+ }
+ result ~= "}";
+ return result;
+ } else static if (is(FullyUnqual!T == enum)) {
+ import std.conv;
+ import std.traits;
+ return to!string(cast(OriginalType!T)value);
+ } else static if (is(FullyUnqual!T == struct) ||
+ is(FullyUnqual!T : TException)
+ ) {
+ string result = "{";
+ foreach (name; __traits(derivedMembers, T)) {
+ static if (memberReq!(T, name) != TReq.IGNORE) {
+ result ~= name ~ ": " ~ dToIdlConst(mixin("value." ~ name)) ~ ", ";
+ }
+ }
+ result ~= "}";
+ return result;
+ } else {
+ static assert(false, "Cannot represent type in Thrift: " ~ T.stringof);
+ }
+ }
+}
+
+version (unittest) {
+ enum Foo {
+ a = 1,
+ b = 10,
+ c = 5
+ }
+
+ static assert(enumIdlString!Foo ==
+`enum Foo {
+ a = 1,
+ b = 10,
+ c = 5,
+}
+`);
+}
+
+
+version (unittest) {
+ struct WithoutMeta {
+ string a;
+ int b;
+ }
+
+ struct WithDefaults {
+ string a = "asdf";
+ double b = 3.1415;
+ WithoutMeta c;
+
+ mixin TStructHelpers!([
+ TFieldMeta("c", 1, TReq.init, `WithoutMeta("foo", 3)`)
+ ]);
+ }
+
+ // These are from DebugProtoTest.thrift.
+ struct OneOfEach {
+ bool im_true;
+ bool im_false;
+ byte a_bite;
+ short integer16;
+ int integer32;
+ long integer64;
+ double double_precision;
+ string some_characters;
+ string zomg_unicode;
+ bool what_who;
+ string base64;
+ byte[] byte_list;
+ short[] i16_list;
+ long[] i64_list;
+
+ mixin TStructHelpers!([
+ TFieldMeta(`im_true`, 1),
+ TFieldMeta(`im_false`, 2),
+ TFieldMeta(`a_bite`, 3, TReq.OPT_IN_REQ_OUT, q{cast(byte)127}),
+ TFieldMeta(`integer16`, 4, TReq.OPT_IN_REQ_OUT, q{cast(short)32767}),
+ TFieldMeta(`integer32`, 5),
+ TFieldMeta(`integer64`, 6, TReq.OPT_IN_REQ_OUT, q{10000000000L}),
+ TFieldMeta(`double_precision`, 7),
+ TFieldMeta(`some_characters`, 8),
+ TFieldMeta(`zomg_unicode`, 9),
+ TFieldMeta(`what_who`, 10),
+ TFieldMeta(`base64`, 11),
+ TFieldMeta(`byte_list`, 12, TReq.OPT_IN_REQ_OUT, q{{
+ byte[] v;
+ v ~= cast(byte)1;
+ v ~= cast(byte)2;
+ v ~= cast(byte)3;
+ return v;
+ }()}),
+ TFieldMeta(`i16_list`, 13, TReq.OPT_IN_REQ_OUT, q{{
+ short[] v;
+ v ~= cast(short)1;
+ v ~= cast(short)2;
+ v ~= cast(short)3;
+ return v;
+ }()}),
+ TFieldMeta(`i64_list`, 14, TReq.OPT_IN_REQ_OUT, q{{
+ long[] v;
+ v ~= 1L;
+ v ~= 2L;
+ v ~= 3L;
+ return v;
+ }()})
+ ]);
+ }
+
+ struct Bonk {
+ int type;
+ string message;
+
+ mixin TStructHelpers!([
+ TFieldMeta(`type`, 1),
+ TFieldMeta(`message`, 2)
+ ]);
+ }
+
+ struct HolyMoley {
+ OneOfEach[] big;
+ HashSet!(string[]) contain;
+ Bonk[][string] bonks;
+
+ mixin TStructHelpers!([
+ TFieldMeta(`big`, 1),
+ TFieldMeta(`contain`, 2),
+ TFieldMeta(`bonks`, 3)
+ ]);
+ }
+
+ static assert(structIdlString!WithoutMeta ==
+`struct WithoutMeta {
+ -1: string a,
+ -2: i32 b,
+}
+`);
+
+ static assert(structIdlString!WithDefaults ==
+`struct WithDefaults {
+ -1: string a = "asdf",
+ -2: double b = 3.1415,
+ 1: WithoutMeta c = {a: "foo", b: 3, },
+}
+`);
+
+ static assert(structIdlString!OneOfEach ==
+`struct OneOfEach {
+ 1: bool im_true,
+ 2: bool im_false,
+ 3: byte a_bite = 127,
+ 4: i16 integer16 = 32767,
+ 5: i32 integer32,
+ 6: i64 integer64 = 10000000000,
+ 7: double double_precision,
+ 8: string some_characters,
+ 9: string zomg_unicode,
+ 10: bool what_who,
+ 11: string base64,
+ 12: list<byte> byte_list = [1, 2, 3, ],
+ 13: list<i16> i16_list = [1, 2, 3, ],
+ 14: list<i64> i64_list = [1, 2, 3, ],
+}
+`);
+
+ static assert(structIdlString!Bonk ==
+`struct Bonk {
+ 1: i32 type,
+ 2: string message,
+}
+`);
+
+ static assert(structIdlString!HolyMoley ==
+`struct HolyMoley {
+ 1: list<OneOfEach> big,
+ 2: set<list<string>> contain,
+ 3: map<string, list<Bonk>> bonks,
+}
+`);
+}
+
+version (unittest) {
+ class ExceptionWithAMap : TException {
+ string blah;
+ string[string] map_field;
+
+ mixin TStructHelpers!([
+ TFieldMeta(`blah`, 1),
+ TFieldMeta(`map_field`, 2)
+ ]);
+ }
+
+ interface Srv {
+ void voidMethod();
+ int primitiveMethod();
+ OneOfEach structMethod();
+ void methodWithDefaultArgs(int something);
+ void onewayMethod();
+ void exceptionMethod();
+
+ alias .ExceptionWithAMap ExceptionWithAMap;
+
+ enum methodMeta = [
+ TMethodMeta(`methodWithDefaultArgs`,
+ [TParamMeta(`something`, 1, q{2})]
+ ),
+ TMethodMeta(`onewayMethod`,
+ [],
+ [],
+ TMethodType.ONEWAY
+ ),
+ TMethodMeta(`exceptionMethod`,
+ [],
+ [
+ TExceptionMeta("a", 1, "ExceptionWithAMap"),
+ TExceptionMeta("b", 2, "ExceptionWithAMap")
+ ]
+ )
+ ];
+ }
+
+ interface ChildSrv : Srv {
+ int childMethod(int arg);
+ }
+
+ static assert(idlString!ChildSrv ==
+`exception ExceptionWithAMap {
+ 1: string blah,
+ 2: map<string, string> map_field,
+}
+
+struct OneOfEach {
+ 1: bool im_true,
+ 2: bool im_false,
+ 3: byte a_bite = 127,
+ 4: i16 integer16 = 32767,
+ 5: i32 integer32,
+ 6: i64 integer64 = 10000000000,
+ 7: double double_precision,
+ 8: string some_characters,
+ 9: string zomg_unicode,
+ 10: bool what_who,
+ 11: string base64,
+ 12: list<byte> byte_list = [1, 2, 3, ],
+ 13: list<i16> i16_list = [1, 2, 3, ],
+ 14: list<i64> i64_list = [1, 2, 3, ],
+}
+
+service Srv {
+ void voidMethod(),
+ i32 primitiveMethod(),
+ OneOfEach structMethod(),
+ void methodWithDefaultArgs(1: i32 something = 2, ),
+ oneway void onewayMethod(),
+ void exceptionMethod() throws (1: ExceptionWithAMap a, 2: ExceptionWithAMap b, ),
+}
+
+service ChildSrv extends Srv {
+ i32 childMethod(-1: i32 param1, ),
+}
+`);
+}