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, ),
+}
+`);
+}