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