| %% | 
 | %% 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. | 
 | %% | 
 | %% The JSON protocol implementation was created by | 
 | %% Peter Neumark <neumark.peter@gmail.com> based on | 
 | %% the binary protocol implementation. | 
 |  | 
 | -module(thrift_json_protocol). | 
 |  | 
 | -behaviour(thrift_protocol). | 
 |  | 
 | -include("thrift_constants.hrl"). | 
 | -include("thrift_protocol.hrl"). | 
 |  | 
 | -export([new/1, new/2, | 
 |          read/2, | 
 |          write/2, | 
 |          flush_transport/1, | 
 |          close_transport/1, | 
 |          new_protocol_factory/2 | 
 |         ]). | 
 |  | 
 | -record(json_context, { | 
 |     % the type of json_context: array or object | 
 |     type, | 
 |     % fields read or written | 
 |     fields_processed = 0 | 
 | }). | 
 |  | 
 | -record(json_protocol, { | 
 |     transport, | 
 |     context_stack = [], | 
 |     jsx | 
 | }). | 
 | -type state() :: #json_protocol{}. | 
 | -include("thrift_protocol_behaviour.hrl"). | 
 |  | 
 | -define(VERSION_1, 1). | 
 | -define(JSON_DOUBLE_PRECISION, 16). | 
 |  | 
 | typeid_to_json(?tType_BOOL) -> "tf"; | 
 | typeid_to_json(?tType_BYTE) -> "i8"; | 
 | typeid_to_json(?tType_DOUBLE) -> "dbl"; | 
 | typeid_to_json(?tType_I16) -> "i16"; | 
 | typeid_to_json(?tType_I32) -> "i32"; | 
 | typeid_to_json(?tType_I64) -> "i64"; | 
 | typeid_to_json(?tType_STRING) -> "str"; | 
 | typeid_to_json(?tType_STRUCT) -> "rec"; | 
 | typeid_to_json(?tType_MAP) -> "map"; | 
 | typeid_to_json(?tType_SET) -> "set"; | 
 | typeid_to_json(?tType_LIST) -> "lst". | 
 |  | 
 | json_to_typeid("tf") -> ?tType_BOOL; | 
 | json_to_typeid("i8") -> ?tType_BYTE; | 
 | json_to_typeid("dbl") -> ?tType_DOUBLE; | 
 | json_to_typeid("i16") -> ?tType_I16; | 
 | json_to_typeid("i32") -> ?tType_I32; | 
 | json_to_typeid("i64") -> ?tType_I64; | 
 | json_to_typeid("str") -> ?tType_STRING; | 
 | json_to_typeid("rec") -> ?tType_STRUCT; | 
 | json_to_typeid("map") -> ?tType_MAP; | 
 | json_to_typeid("set") -> ?tType_SET; | 
 | json_to_typeid("lst") -> ?tType_LIST. | 
 |  | 
 | start_context(object) -> "{"; | 
 | start_context(array) -> "[". | 
 |  | 
 | end_context(object) -> "}"; | 
 | end_context(array) -> "]". | 
 |  | 
 |  | 
 | new(Transport) -> | 
 |     new(Transport, _Options = []). | 
 |  | 
 | new(Transport, _Options) -> | 
 |     State  = #json_protocol{transport = Transport}, | 
 |     thrift_protocol:new(?MODULE, State). | 
 |  | 
 | flush_transport(This = #json_protocol{transport = Transport}) -> | 
 |     {NewTransport, Result} = thrift_transport:flush(Transport), | 
 |     {This#json_protocol{ | 
 |             transport = NewTransport, | 
 |             context_stack = [] | 
 |         }, Result}. | 
 |  | 
 | close_transport(This = #json_protocol{transport = Transport}) -> | 
 |     {NewTransport, Result} = thrift_transport:close(Transport), | 
 |     {This#json_protocol{ | 
 |             transport = NewTransport, | 
 |             context_stack = [], | 
 |             jsx = undefined | 
 |         }, Result}. | 
 |  | 
 | %%% | 
 | %%% instance methods | 
 | %%% | 
 | % places a new context on the stack: | 
 | write(#json_protocol{context_stack = Stack} = State0, {enter_context, Type}) -> | 
 |     {State1, ok} = write_values(State0, [{context_pre_item, false}]), | 
 |     State2 = State1#json_protocol{context_stack = [ | 
 |         #json_context{type=Type}|Stack]}, | 
 |     write_values(State2,  [list_to_binary(start_context(Type))]); | 
 |  | 
 | % removes the topmost context from stack     | 
 | write(#json_protocol{context_stack = [CurrCtxt|Stack]} = State0, {exit_context}) -> | 
 |     Type = CurrCtxt#json_context.type, | 
 |     State1 = State0#json_protocol{context_stack = Stack}, | 
 |     write_values(State1, [ | 
 |             list_to_binary(end_context(Type)), | 
 |             {context_post_item, false} | 
 |         ]); | 
 |  | 
 | % writes necessary prelude to field or container depending on current context    | 
 | write(#json_protocol{context_stack = []} = This0, | 
 |     {context_pre_item, _}) -> {This0, ok}; | 
 | write(#json_protocol{context_stack = [Context|_CtxtTail]} = This0, | 
 |     {context_pre_item, MayNeedQuotes}) -> | 
 |     FieldNo = Context#json_context.fields_processed, | 
 |     CtxtType = Context#json_context.type, | 
 |     Rem = FieldNo rem 2, | 
 |     case {CtxtType, FieldNo, Rem, MayNeedQuotes} of | 
 |         {array, N, _, _} when N > 0 ->  % array element (not first) | 
 |             write(This0, <<",">>); | 
 |         {object, 0, _, true} -> % non-string object key (first) | 
 |             write(This0, <<"\"">>); | 
 |         {object, N, 0, true} when N > 0 -> % non-string object key (not first) | 
 |             write(This0, <<",\"">>); | 
 |         {object, N, 0, false} when N > 0-> % string object key (not first) | 
 |             write(This0, <<",">>); | 
 |         _ -> % no pre-field necessary | 
 |             {This0, ok} | 
 |     end; | 
 |  | 
 | % writes necessary postlude to field or container depending on current context    | 
 | write(#json_protocol{context_stack = []} = This0, | 
 |     {context_post_item, _}) -> {This0, ok}; | 
 | write(#json_protocol{context_stack = [Context|CtxtTail]} = This0, | 
 |     {context_post_item, MayNeedQuotes}) -> | 
 |     FieldNo = Context#json_context.fields_processed, | 
 |     CtxtType = Context#json_context.type, | 
 |     Rem = FieldNo rem 2, | 
 |     {This1, ok} = case {CtxtType, Rem, MayNeedQuotes} of | 
 |         {object, 0, true} -> % non-string object key  | 
 |             write(This0, <<"\":">>); | 
 |         {object, 0, false} -> % string object key  | 
 |             write(This0, <<":">>); | 
 |         _ -> % no pre-field necessary | 
 |             {This0, ok} | 
 |     end, | 
 |     NewContext = Context#json_context{fields_processed = FieldNo + 1}, | 
 |     {This1#json_protocol{context_stack=[NewContext|CtxtTail]}, ok}; | 
 |  | 
 | write(This0, #protocol_message_begin{ | 
 |     name = Name, | 
 |     type = Type, | 
 |     seqid = Seqid}) -> | 
 |     write_values(This0, [ | 
 |         {enter_context, array}, | 
 |         {i32, ?VERSION_1}, | 
 |         {string, Name}, | 
 |         {i32, Type}, | 
 |         {i32, Seqid} | 
 |     ]); | 
 |  | 
 | write(This, message_end) ->   | 
 |     write_values(This, [{exit_context}]); | 
 |  | 
 | % Example field expression: "1":{"dbl":3.14} | 
 | write(This0, #protocol_field_begin{ | 
 |        name = _Name, | 
 |        type = Type, | 
 |        id = Id}) -> | 
 |     write_values(This0, [ | 
 |         % entering 'outer' object | 
 |         {i16, Id}, | 
 |         % entering 'outer' object | 
 |         {enter_context, object}, | 
 |         {string, typeid_to_json(Type)} | 
 |     ]); | 
 |  | 
 | write(This, field_stop) ->  | 
 |     {This, ok}; | 
 |  | 
 | write(This, field_end) ->  | 
 |     write_values(This,[{exit_context}]); | 
 |  | 
 | % Example message with map: [1,"testMap",1,0,{"1":{"map":["i32","i32",3,{"7":77,"8":88,"9":99}]}}] | 
 | write(This0, #protocol_map_begin{ | 
 |        ktype = Ktype, | 
 |        vtype = Vtype, | 
 |        size = Size}) -> | 
 |     write_values(This0, [ | 
 |         {enter_context, array}, | 
 |         {string, typeid_to_json(Ktype)}, | 
 |         {string, typeid_to_json(Vtype)}, | 
 |         {i32, Size}, | 
 |         {enter_context, object} | 
 |     ]); | 
 |  | 
 | write(This, map_end) ->  | 
 |     write_values(This,[ | 
 |         {exit_context}, | 
 |         {exit_context} | 
 |     ]); | 
 |  | 
 | write(This0, #protocol_list_begin{ | 
 |         etype = Etype, | 
 |         size = Size}) -> | 
 |     write_values(This0, [ | 
 |         {enter_context, array}, | 
 |         {string, typeid_to_json(Etype)}, | 
 |         {i32, Size} | 
 |     ]); | 
 |  | 
 | write(This, list_end) ->  | 
 |     write_values(This,[ | 
 |         {exit_context} | 
 |     ]); | 
 |  | 
 | % example message with set: [1,"testSet",1,0,{"1":{"set":["i32",3,1,2,3]}}] | 
 | write(This0, #protocol_set_begin{ | 
 |         etype = Etype, | 
 |         size = Size}) -> | 
 |     write_values(This0, [ | 
 |         {enter_context, array}, | 
 |         {string, typeid_to_json(Etype)}, | 
 |         {i32, Size} | 
 |     ]); | 
 |  | 
 | write(This, set_end) ->  | 
 |     write_values(This,[ | 
 |         {exit_context} | 
 |     ]); | 
 | % example message with struct: [1,"testStruct",1,0,{"1":{"rec":{"1":{"str":"worked"},"4":{"i8":1},"9":{"i32":1073741824},"11":{"i64":1152921504606847000}}}}] | 
 | write(This, #protocol_struct_begin{}) ->  | 
 |     write_values(This, [ | 
 |         {enter_context, object} | 
 |     ]); | 
 |  | 
 | write(This, struct_end) ->  | 
 |     write_values(This,[ | 
 |         {exit_context} | 
 |     ]); | 
 |  | 
 | write(This, {bool, true})  -> write_values(This, [ | 
 |         {context_pre_item, true}, | 
 |         <<"true">>, | 
 |         {context_post_item, true} | 
 |     ]); | 
 |  | 
 | write(This, {bool, false}) -> write_values(This, [ | 
 |         {context_pre_item, true}, | 
 |         <<"false">>, | 
 |         {context_post_item, true} | 
 |     ]); | 
 |  | 
 | write(This, {byte, Byte}) -> write_values(This, [ | 
 |         {context_pre_item, true}, | 
 |         list_to_binary(integer_to_list(Byte)), | 
 |         {context_post_item, true} | 
 |     ]); | 
 |  | 
 | write(This, {i16, I16}) -> | 
 |     write(This, {byte, I16}); | 
 |  | 
 | write(This, {i32, I32}) -> | 
 |     write(This, {byte, I32}); | 
 |  | 
 | write(This, {i64, I64}) -> | 
 |     write(This, {byte, I64}); | 
 |  | 
 | write(This, {double, Double}) -> write_values(This, [ | 
 |         {context_pre_item, true}, | 
 |         list_to_binary(io_lib:format("~.*f", [?JSON_DOUBLE_PRECISION,Double])), | 
 |         {context_post_item, true} | 
 |     ]); | 
 |  | 
 | write(This0, {string, Str}) -> write_values(This0, [ | 
 |         {context_pre_item, false}, | 
 |         case is_binary(Str) of | 
 |             true -> Str; | 
 |             false -> jsx:term_to_json(list_to_binary(Str), [{strict, false}]) | 
 |         end, | 
 |         {context_post_item, false} | 
 |     ]); | 
 |  | 
 | %% TODO: binary fields should be base64 encoded? | 
 |  | 
 | %% Data :: iolist() | 
 | write(This = #json_protocol{transport = Trans}, Data) -> | 
 |     %io:format("Data ~p Ctxt ~p~n~n", [Data, This#json_protocol.context_stack]), | 
 |     {NewTransport, Result} = thrift_transport:write(Trans, Data), | 
 |     {This#json_protocol{transport = NewTransport}, Result}. | 
 |  | 
 | write_values(This0, ValueList) -> | 
 |     FinalState = lists:foldl( | 
 |         fun(Val, ThisIn) -> | 
 |             {ThisOut, ok} = write(ThisIn, Val), | 
 |             ThisOut | 
 |         end, | 
 |         This0, | 
 |         ValueList), | 
 |     {FinalState, ok}. | 
 |  | 
 | %% I wish the erlang version of the transport interface included a  | 
 | %% read_all function (like eg. the java implementation). Since it doesn't, | 
 | %% here's my version (even though it probably shouldn't be in this file). | 
 | %% | 
 | %% The resulting binary is immediately send to the JSX stream parser. | 
 | %% Subsequent calls to read actually operate on the events returned by JSX. | 
 | read_all(#json_protocol{transport = Transport0} = State) -> | 
 |     {Transport1, Bin} = read_all_1(Transport0, []), | 
 |     P = jsx:parser(), | 
 |     State#json_protocol{ | 
 |         transport = Transport1, | 
 |         jsx = P(Bin) | 
 |     }. | 
 |  | 
 | read_all_1(Transport0, IoList) -> | 
 |     {Transport1, Result} = thrift_transport:read(Transport0, 1), | 
 |     case Result of | 
 |         {ok, <<>>} -> % nothing read: assume we're done | 
 |             {Transport1, iolist_to_binary(lists:reverse(IoList))}; | 
 |         {ok, Data} -> % character successfully read; read more | 
 |             read_all_1(Transport1, [Data|IoList]); | 
 |         {error, 'EOF'} -> % we're done | 
 |             {Transport1, iolist_to_binary(lists:reverse(IoList))} | 
 |     end. | 
 |  | 
 | % Expect reads an event from the JSX event stream. It receives an event or data | 
 | % type as input. Comparing the read event from the one is was passed, it | 
 | % returns an error if something other than the expected value is encountered. | 
 | % Expect also maintains the context stack in #json_protocol. | 
 | expect(#json_protocol{jsx={event, {Type, Data}=Ev, Next}}=State, ExpectedType) -> | 
 |     NextState = State#json_protocol{jsx=Next()}, | 
 |     case Type == ExpectedType of | 
 |         true ->  | 
 |             {NextState, {ok, convert_data(Type, Data)}}; | 
 |         false -> | 
 |             {NextState, {error, {unexpected_json_event, Ev}}} | 
 |     end; | 
 |  | 
 | expect(#json_protocol{jsx={event, Event, Next}}=State, ExpectedEvent) -> | 
 |      expect(State#json_protocol{jsx={event, {Event, none}, Next}}, ExpectedEvent). | 
 |  | 
 | convert_data(integer, I) -> list_to_integer(I); | 
 | convert_data(float, F) -> list_to_float(F); | 
 | convert_data(_, D) -> D. | 
 |  | 
 | expect_many(State, ExpectedList) -> | 
 |     expect_many_1(State, ExpectedList, [], ok). | 
 |  | 
 | expect_many_1(State, [], ResultList, Status) -> | 
 |     {State, {Status, lists:reverse(ResultList)}}; | 
 | expect_many_1(State, [Expected|ExpTail], ResultList, _PrevStatus) -> | 
 |     {State1, {Status, Data}} = expect(State, Expected), | 
 |     NewResultList = [Data|ResultList], | 
 |     case Status of | 
 |         % in case of error, end prematurely | 
 |         error -> expect_many_1(State1, [], NewResultList, Status); | 
 |         ok -> expect_many_1(State1, ExpTail, NewResultList, Status) | 
 |     end. | 
 |  | 
 | % wrapper around expect to make life easier for container opening/closing functions | 
 | expect_nodata(This, ExpectedList) -> | 
 |     case expect_many(This, ExpectedList) of | 
 |         {State, {ok, _}} ->  | 
 |             {State, ok}; | 
 |         Error ->  | 
 |             Error | 
 |     end. | 
 |  | 
 | read_field(#json_protocol{jsx={event, Field, Next}} = State) -> | 
 |     NewState = State#json_protocol{jsx=Next()}, | 
 |     {NewState, Field}. | 
 |  | 
 | read(This0, message_begin) -> | 
 |     % call read_all to get the contents of the transport buffer into JSX. | 
 |     This1 = read_all(This0), | 
 |     case expect_many(This1,  | 
 |             [start_array, integer, string, integer, integer]) of | 
 |         {This2, {ok, [_, Version, Name, Type, SeqId]}} -> | 
 |             case Version =:= ?VERSION_1 of | 
 |                 true -> | 
 |                     {This2, #protocol_message_begin{name  = Name, | 
 |                                                     type  = Type, | 
 |                                                     seqid = SeqId}}; | 
 |                 false -> | 
 |                     {This2, {error, no_json_protocol_version}} | 
 |             end; | 
 |         Other -> Other | 
 |     end; | 
 |  | 
 | read(This, message_end) ->  | 
 |     expect_nodata(This, [end_array]); | 
 |  | 
 | read(This, struct_begin) ->  | 
 |     expect_nodata(This, [start_object]); | 
 |  | 
 | read(This, struct_end) ->  | 
 |     expect_nodata(This, [end_object]); | 
 |  | 
 | read(This0, field_begin) -> | 
 |     {This1, Read} = expect_many(This0,  | 
 |             [%field id | 
 |              key,  | 
 |              % {} surrounding field | 
 |              start_object,  | 
 |              % type of field | 
 |              key]), | 
 |     case Read of | 
 |         {ok, [FieldIdStr, _, FieldType]} -> | 
 |             {This1, #protocol_field_begin{ | 
 |                 type = json_to_typeid(FieldType),  | 
 |                 id = list_to_integer(FieldIdStr)}}; % TODO: do we need to wrap this in a try/catch? | 
 |         {error,[{unexpected_json_event, {end_object,none}}]} -> | 
 |             {This1, #protocol_field_begin{type = ?tType_STOP}}; | 
 |         Other ->  | 
 |             io:format("**** OTHER branch selected ****"), | 
 |             {This1, Other} | 
 |     end; | 
 |  | 
 | read(This, field_end) ->  | 
 |     expect_nodata(This, [end_object]); | 
 |  | 
 | % Example message with map: [1,"testMap",1,0,{"1":{"map":["i32","i32",3,{"7":77,"8":88,"9":99}]}}] | 
 | read(This0, map_begin) -> | 
 |     case expect_many(This0,  | 
 |             [start_array, | 
 |              % key type | 
 |              string,  | 
 |              % value type | 
 |              string,  | 
 |              % size | 
 |              integer, | 
 |              % the following object contains the map | 
 |              start_object]) of | 
 |         {This1, {ok, [_, Ktype, Vtype, Size, _]}} -> | 
 |             {This1, #protocol_map_begin{ktype = Ktype, | 
 |                                 vtype = Vtype, | 
 |                                 size = Size}}; | 
 |         Other -> Other | 
 |     end; | 
 |  | 
 | read(This, map_end) ->  | 
 |     expect_nodata(This, [end_object, end_array]); | 
 |  | 
 | read(This0, list_begin) -> | 
 |     case expect_many(This0,  | 
 |             [start_array, | 
 |              % element type | 
 |              string,  | 
 |              % size | 
 |              integer]) of | 
 |         {This1, {ok, [_, Etype, Size]}} -> | 
 |             {This1, #protocol_list_begin{ | 
 |                 etype = Etype, | 
 |                 size = Size}}; | 
 |         Other -> Other | 
 |     end; | 
 |  | 
 | read(This, list_end) ->  | 
 |     expect_nodata(This, [end_array]); | 
 |  | 
 | % example message with set: [1,"testSet",1,0,{"1":{"set":["i32",3,1,2,3]}}] | 
 | read(This0, set_begin) -> | 
 |     case expect_many(This0,  | 
 |             [start_array, | 
 |              % element type | 
 |              string,  | 
 |              % size | 
 |              integer]) of | 
 |         {This1, {ok, [_, Etype, Size]}} -> | 
 |             {This1, #protocol_set_begin{ | 
 |                 etype = Etype, | 
 |                 size = Size}}; | 
 |         Other -> Other | 
 |     end; | 
 |  | 
 | read(This, set_end) ->  | 
 |     expect_nodata(This, [end_array]); | 
 |  | 
 | read(This0, field_stop) -> | 
 |     {This0, ok}; | 
 | %% | 
 |  | 
 | read(This0, bool) -> | 
 |     {This1, Field} = read_field(This0), | 
 |     Value = case Field of | 
 |         {literal, I} ->  | 
 |             {ok, I};  | 
 |         _Other -> | 
 |             {error, unexpected_event_for_boolean} | 
 |     end, | 
 |     {This1, Value}; | 
 |  | 
 | read(This0, byte) -> | 
 |     {This1, Field} = read_field(This0), | 
 |     Value = case Field of | 
 |         {key, K} -> | 
 |             {ok, list_to_integer(K)}; | 
 |         {integer, I} ->  | 
 |             {ok, list_to_integer(I)};  | 
 |         _Other -> | 
 |             {error, unexpected_event_for_integer} | 
 |     end, | 
 |     {This1, Value}; | 
 |  | 
 | read(This0, i16) -> | 
 |     read(This0, byte); | 
 |  | 
 | read(This0, i32) -> | 
 |     read(This0, byte); | 
 |  | 
 | read(This0, i64) -> | 
 |     read(This0, byte); | 
 |  | 
 | read(This0, double) -> | 
 |     {This1, Field} = read_field(This0), | 
 |     Value = case Field of | 
 |         {float, I} ->  | 
 |             {ok, list_to_float(I)};  | 
 |         _Other -> | 
 |             {error, unexpected_event_for_double} | 
 |     end, | 
 |     {This1, Value}; | 
 |  | 
 | % returns a binary directly, call binary_to_list if necessary | 
 | read(This0, string) -> | 
 |     {This1, Field} = read_field(This0), | 
 |     Value = case Field of | 
 |         {string, I} ->  | 
 |             {ok, I};  | 
 |         {key, J} ->  | 
 |             {ok, J};  | 
 |         _Other -> | 
 |             {error, unexpected_event_for_string} | 
 |     end, | 
 |     {This1, Value}. | 
 |  | 
 | %%%% FACTORY GENERATION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | 
 |  | 
 | %% returns a (fun() -> thrift_protocol()) | 
 | new_protocol_factory(TransportFactory, _Options) -> | 
 |     % Only strice read/write are implemented | 
 |     F = fun() -> | 
 |                 {ok, Transport} = TransportFactory(), | 
 |                 thrift_json_protocol:new(Transport, []) | 
 |         end, | 
 |     {ok, F}. | 
 |  |