final class TJsonProtocol()

in lib/d/src/thrift/protocol/json.d [38:757]


final class TJsonProtocol(Transport = TTransport) if (
  isTTransport!Transport
) : TProtocol {
  /**
   * Constructs a new instance.
   *
   * Params:
   *   trans = The transport to use.
   *   containerSizeLimit = If positive, the container size is limited to the
   *     given number of items.
   *   stringSizeLimit = If positive, the string length is limited to the
   *     given number of bytes.
   */
  this(Transport trans, int containerSizeLimit = 0, int stringSizeLimit = 0) {
    trans_ = trans;
    this.containerSizeLimit = containerSizeLimit;
    this.stringSizeLimit = stringSizeLimit;

    context_ = new Context();
    reader_ = new LookaheadReader(trans);
  }

  Transport transport() @property {
    return trans_;
  }

  void reset() {
    destroy(contextStack_);
    context_ = new Context();
    reader_ = new LookaheadReader(trans_);
  }

  /**
   * If positive, limits the number of items of deserialized containers to the
   * given amount.
   *
   * This is useful to avoid allocating excessive amounts of memory when broken
   * data is received. If the limit is exceeded, a SIZE_LIMIT-type
   * TProtocolException is thrown.
   *
   * Defaults to zero (no limit).
   */
  int containerSizeLimit;

  /**
   * If positive, limits the length of deserialized strings/binary data to the
   * given number of bytes.
   *
   * This is useful to avoid allocating excessive amounts of memory when broken
   * data is received. If the limit is exceeded, a SIZE_LIMIT-type
   * TProtocolException is thrown.
   *
   * Note: For binary data, the limit applies to the length of the
   * Base64-encoded string data, not the resulting byte array.
   *
   * Defaults to zero (no limit).
   */
  int stringSizeLimit;

  /*
   * Writing methods.
   */

  void writeBool(bool b) {
    writeJsonInteger(b ? 1 : 0);
  }

  void writeByte(byte b) {
    writeJsonInteger(b);
  }

  void writeI16(short i16) {
    writeJsonInteger(i16);
  }

  void writeI32(int i32) {
    writeJsonInteger(i32);
  }

  void writeI64(long i64) {
    writeJsonInteger(i64);
  }

  void writeDouble(double dub) {
    context_.write(trans_);

    string value;
    if (dub is double.nan) {
      value = NAN_STRING;
    } else if (dub is double.infinity) {
      value = INFINITY_STRING;
    } else if (dub is -double.infinity) {
      value = NEG_INFINITY_STRING;
    }

    bool escapeNum = value !is null || context_.escapeNum;

    if (value is null) {
      /* precision is 17 */
      value = format("%.17g", dub);
    }

    if (escapeNum) trans_.write(STRING_DELIMITER);
    trans_.write(cast(ubyte[])value);
    if (escapeNum) trans_.write(STRING_DELIMITER);
  }

  void writeString(string str) {
    context_.write(trans_);
    trans_.write(STRING_DELIMITER);
    foreach (c; str) {
      writeJsonChar(c);
    }
    trans_.write(STRING_DELIMITER);
  }

  void writeBinary(ubyte[] buf) {
    context_.write(trans_);

    trans_.write(STRING_DELIMITER);
    ubyte[4] b;
    while (!buf.empty) {
      auto toWrite = take(buf, 3);
      Base64NoPad.encode(toWrite, b[]);
      trans_.write(b[0 .. toWrite.length + 1]);
      buf.popFrontN(toWrite.length);
    }
    trans_.write(STRING_DELIMITER);
  }

  void writeMessageBegin(TMessage msg) {
    writeJsonArrayBegin();
    writeJsonInteger(THRIFT_JSON_VERSION);
    writeString(msg.name);
    writeJsonInteger(cast(byte)msg.type);
    writeJsonInteger(msg.seqid);
  }

  void writeMessageEnd() {
    writeJsonArrayEnd();
  }

  void writeStructBegin(TStruct tstruct) {
    writeJsonObjectBegin();
  }

  void writeStructEnd() {
    writeJsonObjectEnd();
  }

  void writeFieldBegin(TField field) {
    writeJsonInteger(field.id);
    writeJsonObjectBegin();
    writeString(getNameFromTType(field.type));
  }

  void writeFieldEnd() {
    writeJsonObjectEnd();
  }

  void writeFieldStop() {}

  void writeListBegin(TList list) {
    writeJsonArrayBegin();
    writeString(getNameFromTType(list.elemType));
    writeJsonInteger(list.size);
  }

  void writeListEnd() {
    writeJsonArrayEnd();
  }

  void writeMapBegin(TMap map) {
    writeJsonArrayBegin();
    writeString(getNameFromTType(map.keyType));
    writeString(getNameFromTType(map.valueType));
    writeJsonInteger(map.size);
    writeJsonObjectBegin();
  }

  void writeMapEnd() {
    writeJsonObjectEnd();
    writeJsonArrayEnd();
  }

  void writeSetBegin(TSet set) {
    writeJsonArrayBegin();
    writeString(getNameFromTType(set.elemType));
    writeJsonInteger(set.size);
  }

  void writeSetEnd() {
    writeJsonArrayEnd();
  }


  /*
   * Reading methods.
   */

  bool readBool() {
    return readJsonInteger!byte() ? true : false;
  }

  byte readByte() {
    return readJsonInteger!byte();
  }

  short readI16() {
    return readJsonInteger!short();
  }

  int readI32() {
    return readJsonInteger!int();
  }

  long readI64() {
    return readJsonInteger!long();
  }

  double readDouble() {
    context_.read(reader_);

    if (reader_.peek() == STRING_DELIMITER) {
      auto str = readJsonString(true);
      if (str == NAN_STRING) {
        return double.nan;
      }
      if (str == INFINITY_STRING) {
        return double.infinity;
      }
      if (str == NEG_INFINITY_STRING) {
        return -double.infinity;
      }

      if (!context_.escapeNum) {
        // Throw exception -- we should not be in a string in this case
        throw new TProtocolException("Numeric data unexpectedly quoted",
          TProtocolException.Type.INVALID_DATA);
      }
      try {
        return to!double(str);
      } catch (ConvException e) {
        throw new TProtocolException(`Expected numeric value; got "` ~ str ~
          `".`, TProtocolException.Type.INVALID_DATA);
      }
    }
    else {
      if (context_.escapeNum) {
        // This will throw - we should have had a quote if escapeNum == true
        readJsonSyntaxChar(STRING_DELIMITER);
      }

      auto str = readJsonNumericChars();
      try {
        return to!double(str);
      } catch (ConvException e) {
        throw new TProtocolException(`Expected numeric value; got "` ~ str ~
          `".`, TProtocolException.Type.INVALID_DATA);
      }
    }
  }

  string readString() {
    return readJsonString(false);
  }

  ubyte[] readBinary() {
    return Base64NoPad.decode(readString());
  }

  TMessage readMessageBegin() {
    TMessage msg = void;

    readJsonArrayBegin();

    auto ver = readJsonInteger!short();
    if (ver != THRIFT_JSON_VERSION) {
      throw new TProtocolException("Message contained bad version.",
        TProtocolException.Type.BAD_VERSION);
    }

    msg.name = readString();
    msg.type = cast(TMessageType)readJsonInteger!byte();
    msg.seqid = readJsonInteger!short();

    return msg;
  }

  void readMessageEnd() {
    readJsonArrayEnd();
  }

  TStruct readStructBegin() {
    readJsonObjectBegin();
    return TStruct();
  }

  void readStructEnd() {
    readJsonObjectEnd();
  }

  TField readFieldBegin() {
    TField f = void;
    f.name = null;

    auto ch = reader_.peek();
    if (ch == OBJECT_END) {
      f.type = TType.STOP;
    } else {
      f.id = readJsonInteger!short();
      readJsonObjectBegin();
      f.type = getTTypeFromName(readString());
    }

    return f;
  }

  void readFieldEnd() {
    readJsonObjectEnd();
  }

  TList readListBegin() {
    readJsonArrayBegin();
    auto type = getTTypeFromName(readString());
    auto size = readContainerSize();
    return TList(type, size);
  }

  void readListEnd() {
    readJsonArrayEnd();
  }

  TMap readMapBegin() {
    readJsonArrayBegin();
    auto keyType = getTTypeFromName(readString());
    auto valueType = getTTypeFromName(readString());
    auto size = readContainerSize();
    readJsonObjectBegin();
    return TMap(keyType, valueType, size);
  }

  void readMapEnd() {
    readJsonObjectEnd();
    readJsonArrayEnd();
  }

  TSet readSetBegin() {
    readJsonArrayBegin();
    auto type = getTTypeFromName(readString());
    auto size = readContainerSize();
    return TSet(type, size);
  }

  void readSetEnd() {
    readJsonArrayEnd();
  }

private:
  void pushContext(Context c) {
    contextStack_ ~= context_;
    context_ = c;
  }

  void popContext() {
    context_ = contextStack_.back;
    contextStack_.popBack();
    contextStack_.assumeSafeAppend();
  }

  /*
   * Writing functions
   */

  // Write the character ch as a Json escape sequence ("\u00xx")
  void writeJsonEscapeChar(ubyte ch) {
    trans_.write(ESCAPE_PREFIX);
    trans_.write(ESCAPE_PREFIX);
    auto outCh = hexChar(cast(ubyte)(ch >> 4));
    trans_.write((&outCh)[0 .. 1]);
    outCh = hexChar(ch);
    trans_.write((&outCh)[0 .. 1]);
  }

  // Write the character ch as part of a Json string, escaping as appropriate.
  void writeJsonChar(ubyte ch) {
    if (ch >= 0x30) {
      if (ch == '\\') { // Only special character >= 0x30 is '\'
        trans_.write(BACKSLASH);
        trans_.write(BACKSLASH);
      } else {
        trans_.write((&ch)[0 .. 1]);
      }
    }
    else {
      auto outCh = kJsonCharTable[ch];
      // Check if regular character, backslash escaped, or Json escaped
      if (outCh == 1) {
        trans_.write((&ch)[0 .. 1]);
      } else if (outCh > 1) {
        trans_.write(BACKSLASH);
        trans_.write((&outCh)[0 .. 1]);
      } else {
        writeJsonEscapeChar(ch);
      }
    }
  }

  // Convert the given integer type to a Json number, or a string
  // if the context requires it (eg: key in a map pair).
  void writeJsonInteger(T)(T num) if (isIntegral!T) {
    context_.write(trans_);

    auto escapeNum = context_.escapeNum();
    if (escapeNum) trans_.write(STRING_DELIMITER);
    trans_.write(cast(ubyte[])to!string(num));
    if (escapeNum) trans_.write(STRING_DELIMITER);
  }

  void writeJsonObjectBegin() {
    context_.write(trans_);
    trans_.write(OBJECT_BEGIN);
    pushContext(new PairContext());
  }

  void writeJsonObjectEnd() {
    popContext();
    trans_.write(OBJECT_END);
  }

  void writeJsonArrayBegin() {
    context_.write(trans_);
    trans_.write(ARRAY_BEGIN);
    pushContext(new ListContext());
  }

  void writeJsonArrayEnd() {
    popContext();
    trans_.write(ARRAY_END);
  }

  /*
   * Reading functions
   */

  int readContainerSize() {
    auto size = readJsonInteger!int();
    if (size < 0) {
      throw new TProtocolException(TProtocolException.Type.NEGATIVE_SIZE);
    } else if (containerSizeLimit > 0 && size > containerSizeLimit) {
      throw new TProtocolException(TProtocolException.Type.SIZE_LIMIT);
    }
    return size;
  }

  void readJsonSyntaxChar(ubyte[1] ch) {
    return readSyntaxChar(reader_, ch);
  }

  wchar readJsonEscapeChar() {
    auto a = reader_.read();
    auto b = reader_.read();
    auto c = reader_.read();
    auto d = reader_.read();
    return cast(ushort)(
          (hexVal(a[0]) << 12) + (hexVal(b[0]) << 8) +
          (hexVal(c[0]) << 4) + hexVal(d[0])
        );
  }

  string readJsonString(bool skipContext = false) {
    if (!skipContext) context_.read(reader_);

    readJsonSyntaxChar(STRING_DELIMITER);
    auto buffer = appender!string();

    wchar[] wchs;
    int bytesRead;
    while (true) {
      auto ch = reader_.read();
      if (ch == STRING_DELIMITER) {
        break;
      }

      ++bytesRead;
      if (stringSizeLimit > 0 && bytesRead > stringSizeLimit) {
        throw new TProtocolException(TProtocolException.Type.SIZE_LIMIT);
      }

      if (ch == BACKSLASH) {
        ch = reader_.read();
        if (ch == ESCAPE_CHAR) {
          auto wch = readJsonEscapeChar();
          if (wch >= 0xD800 && wch <= 0xDBFF) {
            wchs ~= wch;
          } else if (wch >= 0xDC00 && wch <= 0xDFFF && wchs.length == 0) {
            throw new TProtocolException("Missing UTF-16 high surrogate.",
                                         TProtocolException.Type.INVALID_DATA);
          } else {
            wchs ~= wch;
            buffer.put(wchs.toUTF8);
            wchs = [];
          }
          continue;
        } else {
          auto pos = countUntil(kEscapeChars[], ch[0]);
          if (pos == -1) {
            throw new TProtocolException("Expected control char, got '" ~
              cast(char)ch[0] ~ "'.", TProtocolException.Type.INVALID_DATA);
          }
          ch = kEscapeCharVals[pos];
        }
      }
      if (wchs.length != 0) {
        throw new TProtocolException("Missing UTF-16 low surrogate.",
                                     TProtocolException.Type.INVALID_DATA);
      }
      buffer.put(ch[0]);
    }

    if (wchs.length != 0) {
      throw new TProtocolException("Missing UTF-16 low surrogate.",
                                   TProtocolException.Type.INVALID_DATA);
    }
    return buffer.data;
  }

  // Reads a sequence of characters, stopping at the first one that is not
  // a valid Json numeric character.
  string readJsonNumericChars() {
    string str;
    while (true) {
      auto ch = reader_.peek();
      if (!isJsonNumeric(ch[0])) {
        break;
      }
      reader_.read();
      str ~= ch;
    }
    return str;
  }

  // Reads a sequence of characters and assembles them into a number,
  // returning them via num
  T readJsonInteger(T)() if (isIntegral!T) {
    context_.read(reader_);
    if (context_.escapeNum()) {
      readJsonSyntaxChar(STRING_DELIMITER);
    }
    auto str = readJsonNumericChars();
    T num;
    try {
      num = to!T(str);
    } catch (ConvException e) {
      throw new TProtocolException(`Expected numeric value, got "` ~ str ~ `".`,
        TProtocolException.Type.INVALID_DATA);
    }
    if (context_.escapeNum()) {
      readJsonSyntaxChar(STRING_DELIMITER);
    }
    return num;
  }

  void readJsonObjectBegin() {
    context_.read(reader_);
    readJsonSyntaxChar(OBJECT_BEGIN);
    pushContext(new PairContext());
  }

  void readJsonObjectEnd() {
    readJsonSyntaxChar(OBJECT_END);
    popContext();
  }

  void readJsonArrayBegin() {
    context_.read(reader_);
    readJsonSyntaxChar(ARRAY_BEGIN);
    pushContext(new ListContext());
  }

  void readJsonArrayEnd() {
    readJsonSyntaxChar(ARRAY_END);
    popContext();
  }

  static {
    final class LookaheadReader {
      this(Transport trans) {
        trans_ = trans;
      }

      ubyte[1] read() {
        if (hasData_) {
          hasData_ = false;
        } else {
          trans_.readAll(data_);
        }
        return data_;
      }

      ubyte[1] peek() {
        if (!hasData_) {
          trans_.readAll(data_);
          hasData_ = true;
        }
        return data_;
      }

     private:
      Transport trans_;
      bool hasData_;
      ubyte[1] data_;
    }

    /*
     * Class to serve as base Json context and as base class for other context
     * implementations
     */
    class Context {
      /**
       * Write context data to the transport. Default is to do nothing.
       */
      void write(Transport trans) {}

      /**
       * Read context data from the transport. Default is to do nothing.
       */
      void read(LookaheadReader reader) {}

      /**
       * Return true if numbers need to be escaped as strings in this context.
       * Default behavior is to return false.
       */
      bool escapeNum() @property {
        return false;
      }
    }

    // Context class for object member key-value pairs
    class PairContext : Context {
      this() {
        first_ = true;
        colon_ = true;
      }

      override void write(Transport trans) {
        if (first_) {
          first_ = false;
          colon_ = true;
        } else {
          trans.write(colon_ ? PAIR_SEP : ELEM_SEP);
          colon_ = !colon_;
        }
      }

      override void read(LookaheadReader reader) {
        if (first_) {
          first_ = false;
          colon_ = true;
        } else {
          auto ch = (colon_ ? PAIR_SEP : ELEM_SEP);
          colon_ = !colon_;
          return readSyntaxChar(reader, ch);
        }
      }

      // Numbers must be turned into strings if they are the key part of a pair
      override bool escapeNum() @property {
        return colon_;
      }

    private:
      bool first_;
      bool colon_;
    }

    class ListContext : Context {
      this() {
        first_ = true;
      }

      override void write(Transport trans) {
        if (first_) {
          first_ = false;
        } else {
          trans.write(ELEM_SEP);
        }
      }

      override void read(LookaheadReader reader) {
        if (first_) {
          first_ = false;
        } else {
          readSyntaxChar(reader, ELEM_SEP);
        }
      }

    private:
      bool first_;
    }

    // Read 1 character from the transport trans and verify that it is the
    // expected character ch.
    // Throw a protocol exception if it is not.
    void readSyntaxChar(LookaheadReader reader, ubyte[1] ch) {
      auto ch2 = reader.read();
      if (ch2 != ch) {
        throw new TProtocolException("Expected '" ~ cast(char)ch[0] ~ "', got '" ~
          cast(char)ch2[0] ~ "'.", TProtocolException.Type.INVALID_DATA);
      }
    }
  }

  // Probably need to implement a better stack at some point.
  Context[] contextStack_;
  Context context_;

  Transport trans_;
  LookaheadReader reader_;
}