]> git.proxmox.com Git - ceph.git/blame - ceph/src/jaegertracing/thrift/lib/erl/src/thrift_json_protocol.erl
buildsys: switch source download to quincy
[ceph.git] / ceph / src / jaegertracing / thrift / lib / erl / src / thrift_json_protocol.erl
CommitLineData
f67539c2
TL
1%%
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%%
19%% The JSON protocol implementation was created by
20%% Peter Neumark <neumark.peter@gmail.com> based on
21%% the binary protocol implementation.
22
23-module(thrift_json_protocol).
24
25-behaviour(thrift_protocol).
26
27-include("thrift_constants.hrl").
28-include("thrift_protocol.hrl").
29
30-export([new/1, new/2,
31 read/2,
32 write/2,
33 flush_transport/1,
34 close_transport/1,
35 new_protocol_factory/2
36 ]).
37
38-record(json_context, {
39 % the type of json_context: array or object
40 type,
41 % fields read or written
42 fields_processed = 0
43}).
44
45-record(json_protocol, {
46 transport,
47 context_stack = [],
48 jsx
49}).
50-type state() :: #json_protocol{}.
51-include("thrift_protocol_behaviour.hrl").
52
53-define(VERSION_1, 1).
54-define(JSON_DOUBLE_PRECISION, 16).
55
56typeid_to_json(?tType_BOOL) -> "tf";
57typeid_to_json(?tType_BYTE) -> "i8";
58typeid_to_json(?tType_DOUBLE) -> "dbl";
59typeid_to_json(?tType_I8) -> "i8";
60typeid_to_json(?tType_I16) -> "i16";
61typeid_to_json(?tType_I32) -> "i32";
62typeid_to_json(?tType_I64) -> "i64";
63typeid_to_json(?tType_STRING) -> "str";
64typeid_to_json(?tType_STRUCT) -> "rec";
65typeid_to_json(?tType_MAP) -> "map";
66typeid_to_json(?tType_SET) -> "set";
67typeid_to_json(?tType_LIST) -> "lst".
68
69json_to_typeid("tf") -> ?tType_BOOL;
70json_to_typeid("dbl") -> ?tType_DOUBLE;
71json_to_typeid("i8") -> ?tType_I8;
72json_to_typeid("i16") -> ?tType_I16;
73json_to_typeid("i32") -> ?tType_I32;
74json_to_typeid("i64") -> ?tType_I64;
75json_to_typeid("str") -> ?tType_STRING;
76json_to_typeid("rec") -> ?tType_STRUCT;
77json_to_typeid("map") -> ?tType_MAP;
78json_to_typeid("set") -> ?tType_SET;
79json_to_typeid("lst") -> ?tType_LIST.
80
81start_context(object) -> "{";
82start_context(array) -> "[".
83
84end_context(object) -> "}";
85end_context(array) -> "]".
86
87
88new(Transport) ->
89 new(Transport, _Options = []).
90
91new(Transport, _Options) ->
92 State = #json_protocol{transport = Transport},
93 thrift_protocol:new(?MODULE, State).
94
95flush_transport(This = #json_protocol{transport = Transport}) ->
96 {NewTransport, Result} = thrift_transport:flush(Transport),
97 {This#json_protocol{
98 transport = NewTransport,
99 context_stack = []
100 }, Result}.
101
102close_transport(This = #json_protocol{transport = Transport}) ->
103 {NewTransport, Result} = thrift_transport:close(Transport),
104 {This#json_protocol{
105 transport = NewTransport,
106 context_stack = [],
107 jsx = undefined
108 }, Result}.
109
110%%%
111%%% instance methods
112%%%
113% places a new context on the stack:
114write(#json_protocol{context_stack = Stack} = State0, {enter_context, Type}) ->
115 {State1, ok} = write_values(State0, [{context_pre_item, false}]),
116 State2 = State1#json_protocol{context_stack = [
117 #json_context{type=Type}|Stack]},
118 write_values(State2, [list_to_binary(start_context(Type))]);
119
120% removes the topmost context from stack
121write(#json_protocol{context_stack = [CurrCtxt|Stack]} = State0, {exit_context}) ->
122 Type = CurrCtxt#json_context.type,
123 State1 = State0#json_protocol{context_stack = Stack},
124 write_values(State1, [
125 list_to_binary(end_context(Type)),
126 {context_post_item, false}
127 ]);
128
129% writes necessary prelude to field or container depending on current context
130write(#json_protocol{context_stack = []} = This0,
131 {context_pre_item, _}) -> {This0, ok};
132write(#json_protocol{context_stack = [Context|_CtxtTail]} = This0,
133 {context_pre_item, MayNeedQuotes}) ->
134 FieldNo = Context#json_context.fields_processed,
135 CtxtType = Context#json_context.type,
136 Rem = FieldNo rem 2,
137 case {CtxtType, FieldNo, Rem, MayNeedQuotes} of
138 {array, N, _, _} when N > 0 -> % array element (not first)
139 write(This0, <<",">>);
140 {object, 0, _, true} -> % non-string object key (first)
141 write(This0, <<"\"">>);
142 {object, N, 0, true} when N > 0 -> % non-string object key (not first)
143 write(This0, <<",\"">>);
144 {object, N, 0, false} when N > 0-> % string object key (not first)
145 write(This0, <<",">>);
146 _ -> % no pre-field necessary
147 {This0, ok}
148 end;
149
150% writes necessary postlude to field or container depending on current context
151write(#json_protocol{context_stack = []} = This0,
152 {context_post_item, _}) -> {This0, ok};
153write(#json_protocol{context_stack = [Context|CtxtTail]} = This0,
154 {context_post_item, MayNeedQuotes}) ->
155 FieldNo = Context#json_context.fields_processed,
156 CtxtType = Context#json_context.type,
157 Rem = FieldNo rem 2,
158 {This1, ok} = case {CtxtType, Rem, MayNeedQuotes} of
159 {object, 0, true} -> % non-string object key
160 write(This0, <<"\":">>);
161 {object, 0, false} -> % string object key
162 write(This0, <<":">>);
163 _ -> % no pre-field necessary
164 {This0, ok}
165 end,
166 NewContext = Context#json_context{fields_processed = FieldNo + 1},
167 {This1#json_protocol{context_stack=[NewContext|CtxtTail]}, ok};
168
169write(This0, #protocol_message_begin{
170 name = Name,
171 type = Type,
172 seqid = Seqid}) ->
173 write_values(This0, [
174 {enter_context, array},
175 {i32, ?VERSION_1},
176 {string, Name},
177 {i32, Type},
178 {i32, Seqid}
179 ]);
180
181write(This, message_end) ->
182 write_values(This, [{exit_context}]);
183
184% Example field expression: "1":{"dbl":3.14}
185write(This0, #protocol_field_begin{
186 name = _Name,
187 type = Type,
188 id = Id}) ->
189 write_values(This0, [
190 % entering 'outer' object
191 {i16, Id},
192 % entering 'outer' object
193 {enter_context, object},
194 {string, typeid_to_json(Type)}
195 ]);
196
197write(This, field_stop) ->
198 {This, ok};
199
200write(This, field_end) ->
201 write_values(This,[{exit_context}]);
202
203% Example message with map: [1,"testMap",1,0,{"1":{"map":["i32","i32",3,{"7":77,"8":88,"9":99}]}}]
204write(This0, #protocol_map_begin{
205 ktype = Ktype,
206 vtype = Vtype,
207 size = Size}) ->
208 write_values(This0, [
209 {enter_context, array},
210 {string, typeid_to_json(Ktype)},
211 {string, typeid_to_json(Vtype)},
212 {i32, Size},
213 {enter_context, object}
214 ]);
215
216write(This, map_end) ->
217 write_values(This,[
218 {exit_context},
219 {exit_context}
220 ]);
221
222write(This0, #protocol_list_begin{
223 etype = Etype,
224 size = Size}) ->
225 write_values(This0, [
226 {enter_context, array},
227 {string, typeid_to_json(Etype)},
228 {i32, Size}
229 ]);
230
231write(This, list_end) ->
232 write_values(This,[
233 {exit_context}
234 ]);
235
236% example message with set: [1,"testSet",1,0,{"1":{"set":["i32",3,1,2,3]}}]
237write(This0, #protocol_set_begin{
238 etype = Etype,
239 size = Size}) ->
240 write_values(This0, [
241 {enter_context, array},
242 {string, typeid_to_json(Etype)},
243 {i32, Size}
244 ]);
245
246write(This, set_end) ->
247 write_values(This,[
248 {exit_context}
249 ]);
250% example message with struct: [1,"testStruct",1,0,{"1":{"rec":{"1":{"str":"worked"},"4":{"i8":1},"9":{"i32":1073741824},"11":{"i64":1152921504606847000}}}}]
251write(This, #protocol_struct_begin{}) ->
252 write_values(This, [
253 {enter_context, object}
254 ]);
255
256write(This, struct_end) ->
257 write_values(This,[
258 {exit_context}
259 ]);
260
261write(This, {bool, true}) -> write_values(This, [
262 {context_pre_item, true},
263 <<"true">>,
264 {context_post_item, true}
265 ]);
266
267write(This, {bool, false}) -> write_values(This, [
268 {context_pre_item, true},
269 <<"false">>,
270 {context_post_item, true}
271 ]);
272
273write(This, {byte, Byte}) -> write_values(This, [
274 {context_pre_item, true},
275 list_to_binary(integer_to_list(Byte)),
276 {context_post_item, true}
277 ]);
278
279write(This, {i16, I16}) ->
280 write(This, {byte, I16});
281
282write(This, {i32, I32}) ->
283 write(This, {byte, I32});
284
285write(This, {i64, I64}) ->
286 write(This, {byte, I64});
287
288write(This, {double, Double}) -> write_values(This, [
289 {context_pre_item, true},
290 list_to_binary(io_lib:format("~.*f", [?JSON_DOUBLE_PRECISION,Double])),
291 {context_post_item, true}
292 ]);
293
294write(This0, {string, Str}) -> write_values(This0, [
295 {context_pre_item, false},
296 case is_binary(Str) of
297 true -> Str;
298 false -> <<"\"", (list_to_binary(Str))/binary, "\"">>
299 end,
300 {context_post_item, false}
301 ]);
302
303%% TODO: binary fields should be base64 encoded?
304
305%% Data :: iolist()
306write(This = #json_protocol{transport = Trans}, Data) ->
307 %io:format("Data ~p Ctxt ~p~n~n", [Data, This#json_protocol.context_stack]),
308 {NewTransport, Result} = thrift_transport:write(Trans, Data),
309 {This#json_protocol{transport = NewTransport}, Result}.
310
311write_values(This0, ValueList) ->
312 FinalState = lists:foldl(
313 fun(Val, ThisIn) ->
314 {ThisOut, ok} = write(ThisIn, Val),
315 ThisOut
316 end,
317 This0,
318 ValueList),
319 {FinalState, ok}.
320
321%% I wish the erlang version of the transport interface included a
322%% read_all function (like eg. the java implementation). Since it doesn't,
323%% here's my version (even though it probably shouldn't be in this file).
324%%
325%% The resulting binary is immediately send to the JSX stream parser.
326%% Subsequent calls to read actually operate on the events returned by JSX.
327read_all(#json_protocol{transport = Transport0} = State) ->
328 {Transport1, Bin} = read_all_1(Transport0, []),
329 P = thrift_json_parser:parser(),
330 [First|Rest] = P(Bin),
331 State#json_protocol{
332 transport = Transport1,
333 jsx = {event, First, Rest}
334 }.
335
336read_all_1(Transport0, IoList) ->
337 {Transport1, Result} = thrift_transport:read(Transport0, 1),
338 case Result of
339 {ok, <<>>} -> % nothing read: assume we're done
340 {Transport1, iolist_to_binary(lists:reverse(IoList))};
341 {ok, Data} -> % character successfully read; read more
342 read_all_1(Transport1, [Data|IoList]);
343 {error, 'EOF'} -> % we're done
344 {Transport1, iolist_to_binary(lists:reverse(IoList))}
345 end.
346
347% Expect reads an event from the JSX event stream. It receives an event or data
348% type as input. Comparing the read event from the one is was passed, it
349% returns an error if something other than the expected value is encountered.
350% Expect also maintains the context stack in #json_protocol.
351expect(#json_protocol{jsx={event, {Type, Data}=Ev, [Next|Rest]}}=State, ExpectedType) ->
352 NextState = State#json_protocol{jsx={event, Next, Rest}},
353 case Type == ExpectedType of
354 true ->
355 {NextState, {ok, convert_data(Type, Data)}};
356 false ->
357 {NextState, {error, {unexpected_json_event, Ev}}}
358 end;
359
360expect(#json_protocol{jsx={event, Event, Next}}=State, ExpectedEvent) ->
361 expect(State#json_protocol{jsx={event, {Event, none}, Next}}, ExpectedEvent).
362
363convert_data(integer, I) -> list_to_integer(I);
364convert_data(float, F) -> list_to_float(F);
365convert_data(_, D) -> D.
366
367expect_many(State, ExpectedList) ->
368 expect_many_1(State, ExpectedList, [], ok).
369
370expect_many_1(State, [], ResultList, Status) ->
371 {State, {Status, lists:reverse(ResultList)}};
372expect_many_1(State, [Expected|ExpTail], ResultList, _PrevStatus) ->
373 {State1, {Status, Data}} = expect(State, Expected),
374 NewResultList = [Data|ResultList],
375 case Status of
376 % in case of error, end prematurely
377 error -> expect_many_1(State1, [], NewResultList, Status);
378 ok -> expect_many_1(State1, ExpTail, NewResultList, Status)
379 end.
380
381% wrapper around expect to make life easier for container opening/closing functions
382expect_nodata(This, ExpectedList) ->
383 case expect_many(This, ExpectedList) of
384 {State, {ok, _}} ->
385 {State, ok};
386 Error ->
387 Error
388 end.
389
390read_field(#json_protocol{jsx={event, Field, [Next|Rest]}} = State) ->
391 NewState = State#json_protocol{jsx={event, Next, Rest}},
392 {NewState, Field}.
393
394read(This0, message_begin) ->
395 % call read_all to get the contents of the transport buffer into JSX.
396 This1 = read_all(This0),
397 case expect_many(This1,
398 [start_array, integer, string, integer, integer]) of
399 {This2, {ok, [_, Version, Name, Type, SeqId]}} ->
400 case Version =:= ?VERSION_1 of
401 true ->
402 {This2, #protocol_message_begin{name = Name,
403 type = Type,
404 seqid = SeqId}};
405 false ->
406 {This2, {error, no_json_protocol_version}}
407 end;
408 Other -> Other
409 end;
410
411read(This, message_end) ->
412 expect_nodata(This, [end_array]);
413
414read(This, struct_begin) ->
415 expect_nodata(This, [start_object]);
416
417read(This, struct_end) ->
418 expect_nodata(This, [end_object]);
419
420read(This0, field_begin) ->
421 {This1, Read} = expect_many(This0,
422 [%field id
423 key,
424 % {} surrounding field
425 start_object,
426 % type of field
427 key]),
428 case Read of
429 {ok, [FieldIdStr, _, FieldType]} ->
430 {This1, #protocol_field_begin{
431 type = json_to_typeid(FieldType),
432 id = list_to_integer(FieldIdStr)}}; % TODO: do we need to wrap this in a try/catch?
433 {error,[{unexpected_json_event, {end_object,none}}]} ->
434 {This1, #protocol_field_begin{type = ?tType_STOP}};
435 Other ->
436 io:format("**** OTHER branch selected ****"),
437 {This1, Other}
438 end;
439
440read(This, field_end) ->
441 expect_nodata(This, [end_object]);
442
443% Example message with map: [1,"testMap",1,0,{"1":{"map":["i32","i32",3,{"7":77,"8":88,"9":99}]}}]
444read(This0, map_begin) ->
445 case expect_many(This0,
446 [start_array,
447 % key type
448 string,
449 % value type
450 string,
451 % size
452 integer,
453 % the following object contains the map
454 start_object]) of
455 {This1, {ok, [_, Ktype, Vtype, Size, _]}} ->
456 {This1, #protocol_map_begin{ktype = Ktype,
457 vtype = Vtype,
458 size = Size}};
459 Other -> Other
460 end;
461
462read(This, map_end) ->
463 expect_nodata(This, [end_object, end_array]);
464
465read(This0, list_begin) ->
466 case expect_many(This0,
467 [start_array,
468 % element type
469 string,
470 % size
471 integer]) of
472 {This1, {ok, [_, Etype, Size]}} ->
473 {This1, #protocol_list_begin{
474 etype = Etype,
475 size = Size}};
476 Other -> Other
477 end;
478
479read(This, list_end) ->
480 expect_nodata(This, [end_array]);
481
482% example message with set: [1,"testSet",1,0,{"1":{"set":["i32",3,1,2,3]}}]
483read(This0, set_begin) ->
484 case expect_many(This0,
485 [start_array,
486 % element type
487 string,
488 % size
489 integer]) of
490 {This1, {ok, [_, Etype, Size]}} ->
491 {This1, #protocol_set_begin{
492 etype = Etype,
493 size = Size}};
494 Other -> Other
495 end;
496
497read(This, set_end) ->
498 expect_nodata(This, [end_array]);
499
500read(This0, field_stop) ->
501 {This0, ok};
502%%
503
504read(This0, bool) ->
505 {This1, Field} = read_field(This0),
506 Value = case Field of
507 {literal, I} ->
508 {ok, I};
509 _Other ->
510 {error, unexpected_event_for_boolean}
511 end,
512 {This1, Value};
513
514read(This0, byte) ->
515 {This1, Field} = read_field(This0),
516 Value = case Field of
517 {key, K} ->
518 {ok, list_to_integer(K)};
519 {integer, I} ->
520 {ok, list_to_integer(I)};
521 _Other ->
522 {error, unexpected_event_for_integer}
523 end,
524 {This1, Value};
525
526read(This0, i16) ->
527 read(This0, byte);
528
529read(This0, i32) ->
530 read(This0, byte);
531
532read(This0, i64) ->
533 read(This0, byte);
534
535read(This0, double) ->
536 {This1, Field} = read_field(This0),
537 Value = case Field of
538 {float, I} ->
539 {ok, list_to_float(I)};
540 _Other ->
541 {error, unexpected_event_for_double}
542 end,
543 {This1, Value};
544
545% returns a binary directly, call binary_to_list if necessary
546read(This0, string) ->
547 {This1, Field} = read_field(This0),
548 Value = case Field of
549 {string, I} ->
550 {ok, I};
551 {key, J} ->
552 {ok, J};
553 _Other ->
554 {error, unexpected_event_for_string}
555 end,
556 {This1, Value}.
557
558%%%% FACTORY GENERATION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
559
560%% returns a (fun() -> thrift_protocol())
561new_protocol_factory(TransportFactory, _Options) ->
562 % Only strice read/write are implemented
563 F = fun() ->
564 {ok, Transport} = TransportFactory(),
565 thrift_json_protocol:new(Transport, [])
566 end,
567 {ok, F}.