blob: e89aee14e7559582026bf385133f3bae9a4b56fb [file] [log] [blame]
Jake Farrellb95b0ff2012-03-22 21:49:10 +00001/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19module thrift.protocol.json;
20
21import std.algorithm;
22import std.array;
23import std.base64;
24import std.conv;
25import std.range;
26import std.string : format;
27import std.traits : isIntegral;
28import std.typetuple : allSatisfy, TypeTuple;
29import thrift.protocol.base;
30import thrift.transport.base;
31
32alias Base64Impl!('+', '/', Base64.NoPadding) Base64NoPad;
33
34/**
35 * Implementation of the Thrift JSON protocol.
36 */
37final class TJsonProtocol(Transport = TTransport) if (
38 isTTransport!Transport
39) : TProtocol {
40 /**
41 * Constructs a new instance.
42 *
43 * Params:
44 * trans = The transport to use.
45 * containerSizeLimit = If positive, the container size is limited to the
46 * given number of items.
47 * stringSizeLimit = If positive, the string length is limited to the
48 * given number of bytes.
49 */
50 this(Transport trans, int containerSizeLimit = 0, int stringSizeLimit = 0) {
51 trans_ = trans;
52 this.containerSizeLimit = containerSizeLimit;
53 this.stringSizeLimit = stringSizeLimit;
54
55 context_ = new Context();
56 reader_ = new LookaheadReader(trans);
57 }
58
59 Transport transport() @property {
60 return trans_;
61 }
62
63 void reset() {
64 contextStack_.clear();
65 context_ = new Context();
66 reader_ = new LookaheadReader(trans_);
67 }
68
69 /**
70 * If positive, limits the number of items of deserialized containers to the
71 * given amount.
72 *
73 * This is useful to avoid allocating excessive amounts of memory when broken
74 * data is received. If the limit is exceeded, a SIZE_LIMIT-type
75 * TProtocolException is thrown.
76 *
77 * Defaults to zero (no limit).
78 */
79 int containerSizeLimit;
80
81 /**
82 * If positive, limits the length of deserialized strings/binary data to the
83 * given number of bytes.
84 *
85 * This is useful to avoid allocating excessive amounts of memory when broken
86 * data is received. If the limit is exceeded, a SIZE_LIMIT-type
87 * TProtocolException is thrown.
88 *
89 * Note: For binary data, the limit applies to the length of the
90 * Base64-encoded string data, not the resulting byte array.
91 *
92 * Defaults to zero (no limit).
93 */
94 int stringSizeLimit;
95
96 /*
97 * Writing methods.
98 */
99
100 void writeBool(bool b) {
101 writeJsonInteger(b ? 1 : 0);
102 }
103
104 void writeByte(byte b) {
105 writeJsonInteger(b);
106 }
107
108 void writeI16(short i16) {
109 writeJsonInteger(i16);
110 }
111
112 void writeI32(int i32) {
113 writeJsonInteger(i32);
114 }
115
116 void writeI64(long i64) {
117 writeJsonInteger(i64);
118 }
119
120 void writeDouble(double dub) {
121 context_.write(trans_);
122
123 string value;
124 if (dub is double.nan) {
125 value = NAN_STRING;
126 } else if (dub is double.infinity) {
127 value = INFINITY_STRING;
128 } else if (dub is -double.infinity) {
129 value = NEG_INFINITY_STRING;
130 }
131
132 bool escapeNum = value !is null || context_.escapeNum;
133
134 if (value is null) {
135 value = format("%.16g", dub);
136 }
137
138 if (escapeNum) trans_.write(STRING_DELIMITER);
139 trans_.write(cast(ubyte[])value);
140 if (escapeNum) trans_.write(STRING_DELIMITER);
141 }
142
143 void writeString(string str) {
144 context_.write(trans_);
145 trans_.write(STRING_DELIMITER);
146 foreach (c; str) {
147 writeJsonChar(c);
148 }
149 trans_.write(STRING_DELIMITER);
150 }
151
152 void writeBinary(ubyte[] buf) {
153 context_.write(trans_);
154
155 trans_.write(STRING_DELIMITER);
156 ubyte[4] b;
157 while (!buf.empty) {
158 auto toWrite = take(buf, 3);
159 Base64NoPad.encode(toWrite, b[]);
160 trans_.write(b[0 .. toWrite.length + 1]);
161 buf.popFrontN(toWrite.length);
162 }
163 trans_.write(STRING_DELIMITER);
164 }
165
166 void writeMessageBegin(TMessage msg) {
167 writeJsonArrayBegin();
168 writeJsonInteger(THRIFT_JSON_VERSION);
169 writeString(msg.name);
170 writeJsonInteger(cast(byte)msg.type);
171 writeJsonInteger(msg.seqid);
172 }
173
174 void writeMessageEnd() {
175 writeJsonArrayEnd();
176 }
177
178 void writeStructBegin(TStruct tstruct) {
179 writeJsonObjectBegin();
180 }
181
182 void writeStructEnd() {
183 writeJsonObjectEnd();
184 }
185
186 void writeFieldBegin(TField field) {
187 writeJsonInteger(field.id);
188 writeJsonObjectBegin();
189 writeString(getNameFromTType(field.type));
190 }
191
192 void writeFieldEnd() {
193 writeJsonObjectEnd();
194 }
195
196 void writeFieldStop() {}
197
198 void writeListBegin(TList list) {
199 writeJsonArrayBegin();
200 writeString(getNameFromTType(list.elemType));
201 writeJsonInteger(list.size);
202 }
203
204 void writeListEnd() {
205 writeJsonArrayEnd();
206 }
207
208 void writeMapBegin(TMap map) {
209 writeJsonArrayBegin();
210 writeString(getNameFromTType(map.keyType));
211 writeString(getNameFromTType(map.valueType));
212 writeJsonInteger(map.size);
213 writeJsonObjectBegin();
214 }
215
216 void writeMapEnd() {
217 writeJsonObjectEnd();
218 writeJsonArrayEnd();
219 }
220
221 void writeSetBegin(TSet set) {
222 writeJsonArrayBegin();
223 writeString(getNameFromTType(set.elemType));
224 writeJsonInteger(set.size);
225 }
226
227 void writeSetEnd() {
228 writeJsonArrayEnd();
229 }
230
231
232 /*
233 * Reading methods.
234 */
235
236 bool readBool() {
237 return readJsonInteger!byte() ? true : false;
238 }
239
240 byte readByte() {
241 return readJsonInteger!byte();
242 }
243
244 short readI16() {
245 return readJsonInteger!short();
246 }
247
248 int readI32() {
249 return readJsonInteger!int();
250 }
251
252 long readI64() {
253 return readJsonInteger!long();
254 }
255
256 double readDouble() {
257 context_.read(reader_);
258
259 if (reader_.peek() == STRING_DELIMITER) {
260 auto str = readJsonString(true);
261 if (str == NAN_STRING) {
262 return double.nan;
263 }
264 if (str == INFINITY_STRING) {
265 return double.infinity;
266 }
267 if (str == NEG_INFINITY_STRING) {
268 return -double.infinity;
269 }
270
271 if (!context_.escapeNum) {
272 // Throw exception -- we should not be in a string in this case
273 throw new TProtocolException("Numeric data unexpectedly quoted",
274 TProtocolException.Type.INVALID_DATA);
275 }
276 try {
277 return to!double(str);
278 } catch (ConvException e) {
279 throw new TProtocolException(`Expected numeric value; got "` ~ str ~
280 `".`, TProtocolException.Type.INVALID_DATA);
281 }
282 }
283 else {
284 if (context_.escapeNum) {
285 // This will throw - we should have had a quote if escapeNum == true
286 readJsonSyntaxChar(STRING_DELIMITER);
287 }
288
289 auto str = readJsonNumericChars();
290 try {
291 return to!double(str);
292 } catch (ConvException e) {
293 throw new TProtocolException(`Expected numeric value; got "` ~ str ~
294 `".`, TProtocolException.Type.INVALID_DATA);
295 }
296 }
297 }
298
299 string readString() {
300 return readJsonString(false);
301 }
302
303 ubyte[] readBinary() {
304 return Base64NoPad.decode(readString());
305 }
306
307 TMessage readMessageBegin() {
308 TMessage msg = void;
309
310 readJsonArrayBegin();
311
312 auto ver = readJsonInteger!short();
313 if (ver != THRIFT_JSON_VERSION) {
314 throw new TProtocolException("Message contained bad version.",
315 TProtocolException.Type.BAD_VERSION);
316 }
317
318 msg.name = readString();
319 msg.type = cast(TMessageType)readJsonInteger!byte();
320 msg.seqid = readJsonInteger!short();
321
322 return msg;
323 }
324
325 void readMessageEnd() {
326 readJsonArrayEnd();
327 }
328
329 TStruct readStructBegin() {
330 readJsonObjectBegin();
331 return TStruct();
332 }
333
334 void readStructEnd() {
335 readJsonObjectEnd();
336 }
337
338 TField readFieldBegin() {
339 TField f = void;
340 f.name = null;
341
342 auto ch = reader_.peek();
343 if (ch == OBJECT_END) {
344 f.type = TType.STOP;
345 } else {
346 f.id = readJsonInteger!short();
347 readJsonObjectBegin();
348 f.type = getTTypeFromName(readString());
349 }
350
351 return f;
352 }
353
354 void readFieldEnd() {
355 readJsonObjectEnd();
356 }
357
358 TList readListBegin() {
359 readJsonArrayBegin();
360 auto type = getTTypeFromName(readString());
361 auto size = readContainerSize();
362 return TList(type, size);
363 }
364
365 void readListEnd() {
366 readJsonArrayEnd();
367 }
368
369 TMap readMapBegin() {
370 readJsonArrayBegin();
371 auto keyType = getTTypeFromName(readString());
372 auto valueType = getTTypeFromName(readString());
373 auto size = readContainerSize();
374 readJsonObjectBegin();
375 return TMap(keyType, valueType, size);
376 }
377
378 void readMapEnd() {
379 readJsonObjectEnd();
380 readJsonArrayEnd();
381 }
382
383 TSet readSetBegin() {
384 readJsonArrayBegin();
385 auto type = getTTypeFromName(readString());
386 auto size = readContainerSize();
387 return TSet(type, size);
388 }
389
390 void readSetEnd() {
391 readJsonArrayEnd();
392 }
393
394private:
395 void pushContext(Context c) {
396 contextStack_ ~= context_;
397 context_ = c;
398 }
399
400 void popContext() {
401 context_ = contextStack_.back;
402 contextStack_.popBack();
403 contextStack_.assumeSafeAppend();
404 }
405
406 /*
407 * Writing functions
408 */
409
410 // Write the character ch as a Json escape sequence ("\u00xx")
411 void writeJsonEscapeChar(ubyte ch) {
412 trans_.write(ESCAPE_PREFIX);
413 trans_.write(ESCAPE_PREFIX);
414 auto outCh = hexChar(cast(ubyte)(ch >> 4));
415 trans_.write((&outCh)[0 .. 1]);
416 outCh = hexChar(ch);
417 trans_.write((&outCh)[0 .. 1]);
418 }
419
420 // Write the character ch as part of a Json string, escaping as appropriate.
421 void writeJsonChar(ubyte ch) {
422 if (ch >= 0x30) {
423 if (ch == '\\') { // Only special character >= 0x30 is '\'
424 trans_.write(BACKSLASH);
425 trans_.write(BACKSLASH);
426 } else {
427 trans_.write((&ch)[0 .. 1]);
428 }
429 }
430 else {
431 auto outCh = kJsonCharTable[ch];
432 // Check if regular character, backslash escaped, or Json escaped
433 if (outCh == 1) {
434 trans_.write((&ch)[0 .. 1]);
435 } else if (outCh > 1) {
436 trans_.write(BACKSLASH);
437 trans_.write((&outCh)[0 .. 1]);
438 } else {
439 writeJsonEscapeChar(ch);
440 }
441 }
442 }
443
444 // Convert the given integer type to a Json number, or a string
445 // if the context requires it (eg: key in a map pair).
446 void writeJsonInteger(T)(T num) if (isIntegral!T) {
447 context_.write(trans_);
448
449 auto escapeNum = context_.escapeNum();
450 if (escapeNum) trans_.write(STRING_DELIMITER);
451 trans_.write(cast(ubyte[])to!string(num));
452 if (escapeNum) trans_.write(STRING_DELIMITER);
453 }
454
455 void writeJsonObjectBegin() {
456 context_.write(trans_);
457 trans_.write(OBJECT_BEGIN);
458 pushContext(new PairContext());
459 }
460
461 void writeJsonObjectEnd() {
462 popContext();
463 trans_.write(OBJECT_END);
464 }
465
466 void writeJsonArrayBegin() {
467 context_.write(trans_);
468 trans_.write(ARRAY_BEGIN);
469 pushContext(new ListContext());
470 }
471
472 void writeJsonArrayEnd() {
473 popContext();
474 trans_.write(ARRAY_END);
475 }
476
477 /*
478 * Reading functions
479 */
480
481 int readContainerSize() {
482 auto size = readJsonInteger!int();
483 if (size < 0) {
484 throw new TProtocolException(TProtocolException.Type.NEGATIVE_SIZE);
485 } else if (containerSizeLimit > 0 && size > containerSizeLimit) {
486 throw new TProtocolException(TProtocolException.Type.SIZE_LIMIT);
487 }
488 return size;
489 }
490
491 void readJsonSyntaxChar(ubyte[1] ch) {
492 return readSyntaxChar(reader_, ch);
493 }
494
495 ubyte readJsonEscapeChar() {
496 readJsonSyntaxChar(ZERO_CHAR);
497 readJsonSyntaxChar(ZERO_CHAR);
498 auto a = reader_.read();
499 auto b = reader_.read();
500 return cast(ubyte)((hexVal(a[0]) << 4) + hexVal(b[0]));
501 }
502
503 string readJsonString(bool skipContext = false) {
504 if (!skipContext) context_.read(reader_);
505
506 readJsonSyntaxChar(STRING_DELIMITER);
507 auto buffer = appender!string();
508
509 int bytesRead;
510 while (true) {
511 auto ch = reader_.read();
512 if (ch == STRING_DELIMITER) {
513 break;
514 }
515
516 ++bytesRead;
517 if (stringSizeLimit > 0 && bytesRead > stringSizeLimit) {
518 throw new TProtocolException(TProtocolException.Type.SIZE_LIMIT);
519 }
520
521 if (ch == BACKSLASH) {
522 ch = reader_.read();
523 if (ch == ESCAPE_CHAR) {
524 ch = readJsonEscapeChar();
525 } else {
526 auto pos = countUntil(kEscapeChars[], ch[0]);
527 if (pos == -1) {
528 throw new TProtocolException("Expected control char, got '" ~
529 cast(char)ch[0] ~ "'.", TProtocolException.Type.INVALID_DATA);
530 }
531 ch = kEscapeCharVals[pos];
532 }
533 }
534 buffer.put(ch[0]);
535 }
536
537 return buffer.data;
538 }
539
540 // Reads a sequence of characters, stopping at the first one that is not
541 // a valid Json numeric character.
542 string readJsonNumericChars() {
543 string str;
544 while (true) {
545 auto ch = reader_.peek();
546 if (!isJsonNumeric(ch[0])) {
547 break;
548 }
549 reader_.read();
550 str ~= ch;
551 }
552 return str;
553 }
554
555 // Reads a sequence of characters and assembles them into a number,
556 // returning them via num
557 T readJsonInteger(T)() if (isIntegral!T) {
558 context_.read(reader_);
559 if (context_.escapeNum()) {
560 readJsonSyntaxChar(STRING_DELIMITER);
561 }
562 auto str = readJsonNumericChars();
563 T num;
564 try {
565 num = to!T(str);
566 } catch (ConvException e) {
567 throw new TProtocolException(`Expected numeric value, got "` ~ str ~ `".`,
568 TProtocolException.Type.INVALID_DATA);
569 }
570 if (context_.escapeNum()) {
571 readJsonSyntaxChar(STRING_DELIMITER);
572 }
573 return num;
574 }
575
576 void readJsonObjectBegin() {
577 context_.read(reader_);
578 readJsonSyntaxChar(OBJECT_BEGIN);
579 pushContext(new PairContext());
580 }
581
582 void readJsonObjectEnd() {
583 readJsonSyntaxChar(OBJECT_END);
584 popContext();
585 }
586
587 void readJsonArrayBegin() {
588 context_.read(reader_);
589 readJsonSyntaxChar(ARRAY_BEGIN);
590 pushContext(new ListContext());
591 }
592
593 void readJsonArrayEnd() {
594 readJsonSyntaxChar(ARRAY_END);
595 popContext();
596 }
597
598 static {
599 final class LookaheadReader {
600 this(Transport trans) {
601 trans_ = trans;
602 }
603
604 ubyte[1] read() {
605 if (hasData_) {
606 hasData_ = false;
607 } else {
608 trans_.readAll(data_);
609 }
610 return data_;
611 }
612
613 ubyte[1] peek() {
614 if (!hasData_) {
615 trans_.readAll(data_);
616 hasData_ = true;
617 }
618 return data_;
619 }
620
621 private:
622 Transport trans_;
623 bool hasData_;
624 ubyte[1] data_;
625 }
626
627 /*
628 * Class to serve as base Json context and as base class for other context
629 * implementations
630 */
631 class Context {
632 /**
633 * Write context data to the transport. Default is to do nothing.
634 */
635 void write(Transport trans) {}
636
637 /**
638 * Read context data from the transport. Default is to do nothing.
639 */
640 void read(LookaheadReader reader) {}
641
642 /**
643 * Return true if numbers need to be escaped as strings in this context.
644 * Default behavior is to return false.
645 */
646 bool escapeNum() @property {
647 return false;
648 }
649 }
650
651 // Context class for object member key-value pairs
652 class PairContext : Context {
653 this() {
654 first_ = true;
655 colon_ = true;
656 }
657
658 override void write(Transport trans) {
659 if (first_) {
660 first_ = false;
661 colon_ = true;
662 } else {
663 trans.write(colon_ ? PAIR_SEP : ELEM_SEP);
664 colon_ = !colon_;
665 }
666 }
667
668 override void read(LookaheadReader reader) {
669 if (first_) {
670 first_ = false;
671 colon_ = true;
672 } else {
673 auto ch = (colon_ ? PAIR_SEP : ELEM_SEP);
674 colon_ = !colon_;
675 return readSyntaxChar(reader, ch);
676 }
677 }
678
679 // Numbers must be turned into strings if they are the key part of a pair
680 override bool escapeNum() @property {
681 return colon_;
682 }
683
684 private:
685 bool first_;
686 bool colon_;
687 }
688
689 class ListContext : Context {
690 this() {
691 first_ = true;
692 }
693
694 override void write(Transport trans) {
695 if (first_) {
696 first_ = false;
697 } else {
698 trans.write(ELEM_SEP);
699 }
700 }
701
702 override void read(LookaheadReader reader) {
703 if (first_) {
704 first_ = false;
705 } else {
706 readSyntaxChar(reader, ELEM_SEP);
707 }
708 }
709
710 private:
711 bool first_;
712 }
713
714 // Read 1 character from the transport trans and verify that it is the
715 // expected character ch.
716 // Throw a protocol exception if it is not.
717 void readSyntaxChar(LookaheadReader reader, ubyte[1] ch) {
718 auto ch2 = reader.read();
719 if (ch2 != ch) {
720 throw new TProtocolException("Expected '" ~ cast(char)ch[0] ~ "', got '" ~
721 cast(char)ch2[0] ~ "'.", TProtocolException.Type.INVALID_DATA);
722 }
723 }
724 }
725
726 // Probably need to implement a better stack at some point.
727 Context[] contextStack_;
728 Context context_;
729
730 Transport trans_;
731 LookaheadReader reader_;
732}
733
734/**
735 * TJsonProtocol construction helper to avoid having to explicitly specify
736 * the transport type, i.e. to allow the constructor being called using IFTI
737 * (see $(LINK2 http://d.puremagic.com/issues/show_bug.cgi?id=6082, D Bugzilla
738 * enhancement requet 6082)).
739 */
740TJsonProtocol!Transport tJsonProtocol(Transport)(Transport trans,
741 int containerSizeLimit = 0, int stringSizeLimit = 0
742) if (isTTransport!Transport) {
743 return new TJsonProtocol!Transport(trans, containerSizeLimit, stringSizeLimit);
744}
745
746unittest {
747 import std.exception;
748 import thrift.transport.memory;
749
750 // Check the message header format.
751 auto buf = new TMemoryBuffer;
752 auto json = tJsonProtocol(buf);
753 json.writeMessageBegin(TMessage("foo", TMessageType.CALL, 0));
754 json.writeMessageEnd();
755
756 auto header = new ubyte[13];
757 buf.readAll(header);
758 enforce(cast(char[])header == `[1,"foo",1,0]`);
759}
760
761unittest {
762 import std.exception;
763 import thrift.transport.memory;
764
765 // Check that short binary data is read correctly (the Thrift JSON format
766 // does not include padding chars in the Base64 encoded data).
767 auto buf = new TMemoryBuffer;
768 auto json = tJsonProtocol(buf);
769 json.writeBinary([1, 2]);
770 json.reset();
771 enforce(json.readBinary() == [1, 2]);
772}
773
774unittest {
775 import thrift.internal.test.protocol;
776 testContainerSizeLimit!(TJsonProtocol!())();
777 testStringSizeLimit!(TJsonProtocol!())();
778}
779
780/**
781 * TProtocolFactory creating a TJsonProtocol instance for passed in
782 * transports.
783 *
784 * The optional Transports template tuple parameter can be used to specify
785 * one or more TTransport implementations to specifically instantiate
786 * TJsonProtocol for. If the actual transport types encountered at
787 * runtime match one of the transports in the list, a specialized protocol
788 * instance is created. Otherwise, a generic TTransport version is used.
789 */
790class TJsonProtocolFactory(Transports...) if (
791 allSatisfy!(isTTransport, Transports)
792) : TProtocolFactory {
793 TProtocol getProtocol(TTransport trans) const {
794 foreach (Transport; TypeTuple!(Transports, TTransport)) {
795 auto concreteTrans = cast(Transport)trans;
796 if (concreteTrans) {
797 auto p = new TJsonProtocol!Transport(concreteTrans);
798 return p;
799 }
800 }
801 throw new TProtocolException(
802 "Passed null transport to TJsonProtocolFactoy.");
803 }
804}
805
806private {
807 immutable ubyte[1] OBJECT_BEGIN = '{';
808 immutable ubyte[1] OBJECT_END = '}';
809 immutable ubyte[1] ARRAY_BEGIN = '[';
810 immutable ubyte[1] ARRAY_END = ']';
811 immutable ubyte[1] NEWLINE = '\n';
812 immutable ubyte[1] PAIR_SEP = ':';
813 immutable ubyte[1] ELEM_SEP = ',';
814 immutable ubyte[1] BACKSLASH = '\\';
815 immutable ubyte[1] STRING_DELIMITER = '"';
816 immutable ubyte[1] ZERO_CHAR = '0';
817 immutable ubyte[1] ESCAPE_CHAR = 'u';
818 immutable ubyte[4] ESCAPE_PREFIX = cast(ubyte[4])r"\u00";
819
820 enum THRIFT_JSON_VERSION = 1;
821
822 immutable NAN_STRING = "NaN";
823 immutable INFINITY_STRING = "Infinity";
824 immutable NEG_INFINITY_STRING = "-Infinity";
825
826 string getNameFromTType(TType typeID) {
827 final switch (typeID) {
828 case TType.BOOL:
829 return "tf";
830 case TType.BYTE:
831 return "i8";
832 case TType.I16:
833 return "i16";
834 case TType.I32:
835 return "i32";
836 case TType.I64:
837 return "i64";
838 case TType.DOUBLE:
839 return "dbl";
840 case TType.STRING:
841 return "str";
842 case TType.STRUCT:
843 return "rec";
844 case TType.MAP:
845 return "map";
846 case TType.LIST:
847 return "lst";
848 case TType.SET:
849 return "set";
Jake Farrellc02efe22012-08-18 03:31:28 +0000850 case TType.STOP: goto case;
851 case TType.VOID:
852 assert(false, "Invalid type passed.");
Jake Farrellb95b0ff2012-03-22 21:49:10 +0000853 }
854 }
855
856 TType getTTypeFromName(string name) {
857 TType result;
858 if (name.length > 1) {
859 switch (name[0]) {
860 case 'd':
861 result = TType.DOUBLE;
862 break;
863 case 'i':
864 switch (name[1]) {
865 case '8':
866 result = TType.BYTE;
867 break;
868 case '1':
869 result = TType.I16;
870 break;
871 case '3':
872 result = TType.I32;
873 break;
874 case '6':
875 result = TType.I64;
876 break;
877 default:
878 // Do nothing.
879 }
880 break;
881 case 'l':
882 result = TType.LIST;
883 break;
884 case 'm':
885 result = TType.MAP;
886 break;
887 case 'r':
888 result = TType.STRUCT;
889 break;
890 case 's':
891 if (name[1] == 't') {
892 result = TType.STRING;
893 }
894 else if (name[1] == 'e') {
895 result = TType.SET;
896 }
897 break;
898 case 't':
899 result = TType.BOOL;
900 break;
901 default:
902 // Do nothing.
903 }
904 }
905 if (result == TType.STOP) {
906 throw new TProtocolException("Unrecognized type",
907 TProtocolException.Type.NOT_IMPLEMENTED);
908 }
909 return result;
910 }
911
912 // This table describes the handling for the first 0x30 characters
913 // 0 : escape using "\u00xx" notation
914 // 1 : just output index
915 // <other> : escape using "\<other>" notation
916 immutable ubyte[0x30] kJsonCharTable = [
917 // 0 1 2 3 4 5 6 7 8 9 A B C D E F
918 0, 0, 0, 0, 0, 0, 0, 0,'b','t','n', 0,'f','r', 0, 0, // 0
919 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1
920 1, 1,'"', 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2
921 ];
922
923 // This string's characters must match up with the elements in kEscapeCharVals.
924 // I don't have '/' on this list even though it appears on www.json.org --
925 // it is not in the RFC
926 immutable kEscapeChars = cast(ubyte[7]) `"\\bfnrt`;
927
928 // The elements of this array must match up with the sequence of characters in
929 // kEscapeChars
930 immutable ubyte[7] kEscapeCharVals = [
931 '"', '\\', '\b', '\f', '\n', '\r', '\t',
932 ];
933
934 // Return the integer value of a hex character ch.
935 // Throw a protocol exception if the character is not [0-9a-f].
936 ubyte hexVal(ubyte ch) {
937 if ((ch >= '0') && (ch <= '9')) {
938 return cast(ubyte)(ch - '0');
939 } else if ((ch >= 'a') && (ch <= 'f')) {
940 return cast(ubyte)(ch - 'a' + 10);
941 }
942 else {
943 throw new TProtocolException("Expected hex val ([0-9a-f]), got '" ~
944 ch ~ "'.", TProtocolException.Type.INVALID_DATA);
945 }
946 }
947
948 // Return the hex character representing the integer val. The value is masked
949 // to make sure it is in the correct range.
950 ubyte hexChar(ubyte val) {
951 val &= 0x0F;
952 if (val < 10) {
953 return cast(ubyte)(val + '0');
954 } else {
955 return cast(ubyte)(val - 10 + 'a');
956 }
957 }
958
959 // Return true if the character ch is in [-+0-9.Ee]; false otherwise
960 bool isJsonNumeric(ubyte ch) {
961 switch (ch) {
962 case '+':
963 case '-':
964 case '.':
965 case '0':
966 case '1':
967 case '2':
968 case '3':
969 case '4':
970 case '5':
971 case '6':
972 case '7':
973 case '8':
974 case '9':
975 case 'E':
976 case 'e':
977 return true;
978 default:
979 return false;
980 }
981 }
982}