summaryrefslogtreecommitdiffstats
path: root/src/jaegertracing/thrift/lib/erl/src/thrift_json_protocol.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/jaegertracing/thrift/lib/erl/src/thrift_json_protocol.erl')
-rw-r--r--src/jaegertracing/thrift/lib/erl/src/thrift_json_protocol.erl567
1 files changed, 567 insertions, 0 deletions
diff --git a/src/jaegertracing/thrift/lib/erl/src/thrift_json_protocol.erl b/src/jaegertracing/thrift/lib/erl/src/thrift_json_protocol.erl
new file mode 100644
index 000000000..c5f3da838
--- /dev/null
+++ b/src/jaegertracing/thrift/lib/erl/src/thrift_json_protocol.erl
@@ -0,0 +1,567 @@
+%%
+%% 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_I8) -> "i8";
+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("dbl") -> ?tType_DOUBLE;
+json_to_typeid("i8") -> ?tType_I8;
+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 -> <<"\"", (list_to_binary(Str))/binary, "\"">>
+ 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 = thrift_json_parser:parser(),
+ [First|Rest] = P(Bin),
+ State#json_protocol{
+ transport = Transport1,
+ jsx = {event, First, Rest}
+ }.
+
+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|Rest]}}=State, ExpectedType) ->
+ NextState = State#json_protocol{jsx={event, Next, Rest}},
+ 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|Rest]}} = State) ->
+ NewState = State#json_protocol{jsx={event, Next, Rest}},
+ {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}.