]>
Commit | Line | Data |
---|---|---|
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 | ||
56 | typeid_to_json(?tType_BOOL) -> "tf"; | |
57 | typeid_to_json(?tType_BYTE) -> "i8"; | |
58 | typeid_to_json(?tType_DOUBLE) -> "dbl"; | |
59 | typeid_to_json(?tType_I8) -> "i8"; | |
60 | typeid_to_json(?tType_I16) -> "i16"; | |
61 | typeid_to_json(?tType_I32) -> "i32"; | |
62 | typeid_to_json(?tType_I64) -> "i64"; | |
63 | typeid_to_json(?tType_STRING) -> "str"; | |
64 | typeid_to_json(?tType_STRUCT) -> "rec"; | |
65 | typeid_to_json(?tType_MAP) -> "map"; | |
66 | typeid_to_json(?tType_SET) -> "set"; | |
67 | typeid_to_json(?tType_LIST) -> "lst". | |
68 | ||
69 | json_to_typeid("tf") -> ?tType_BOOL; | |
70 | json_to_typeid("dbl") -> ?tType_DOUBLE; | |
71 | json_to_typeid("i8") -> ?tType_I8; | |
72 | json_to_typeid("i16") -> ?tType_I16; | |
73 | json_to_typeid("i32") -> ?tType_I32; | |
74 | json_to_typeid("i64") -> ?tType_I64; | |
75 | json_to_typeid("str") -> ?tType_STRING; | |
76 | json_to_typeid("rec") -> ?tType_STRUCT; | |
77 | json_to_typeid("map") -> ?tType_MAP; | |
78 | json_to_typeid("set") -> ?tType_SET; | |
79 | json_to_typeid("lst") -> ?tType_LIST. | |
80 | ||
81 | start_context(object) -> "{"; | |
82 | start_context(array) -> "[". | |
83 | ||
84 | end_context(object) -> "}"; | |
85 | end_context(array) -> "]". | |
86 | ||
87 | ||
88 | new(Transport) -> | |
89 | new(Transport, _Options = []). | |
90 | ||
91 | new(Transport, _Options) -> | |
92 | State = #json_protocol{transport = Transport}, | |
93 | thrift_protocol:new(?MODULE, State). | |
94 | ||
95 | flush_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 | ||
102 | close_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: | |
114 | write(#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 | |
121 | write(#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 | |
130 | write(#json_protocol{context_stack = []} = This0, | |
131 | {context_pre_item, _}) -> {This0, ok}; | |
132 | write(#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 | |
151 | write(#json_protocol{context_stack = []} = This0, | |
152 | {context_post_item, _}) -> {This0, ok}; | |
153 | write(#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 | ||
169 | write(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 | ||
181 | write(This, message_end) -> | |
182 | write_values(This, [{exit_context}]); | |
183 | ||
184 | % Example field expression: "1":{"dbl":3.14} | |
185 | write(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 | ||
197 | write(This, field_stop) -> | |
198 | {This, ok}; | |
199 | ||
200 | write(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}]}}] | |
204 | write(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 | ||
216 | write(This, map_end) -> | |
217 | write_values(This,[ | |
218 | {exit_context}, | |
219 | {exit_context} | |
220 | ]); | |
221 | ||
222 | write(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 | ||
231 | write(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]}}] | |
237 | write(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 | ||
246 | write(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}}}}] | |
251 | write(This, #protocol_struct_begin{}) -> | |
252 | write_values(This, [ | |
253 | {enter_context, object} | |
254 | ]); | |
255 | ||
256 | write(This, struct_end) -> | |
257 | write_values(This,[ | |
258 | {exit_context} | |
259 | ]); | |
260 | ||
261 | write(This, {bool, true}) -> write_values(This, [ | |
262 | {context_pre_item, true}, | |
263 | <<"true">>, | |
264 | {context_post_item, true} | |
265 | ]); | |
266 | ||
267 | write(This, {bool, false}) -> write_values(This, [ | |
268 | {context_pre_item, true}, | |
269 | <<"false">>, | |
270 | {context_post_item, true} | |
271 | ]); | |
272 | ||
273 | write(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 | ||
279 | write(This, {i16, I16}) -> | |
280 | write(This, {byte, I16}); | |
281 | ||
282 | write(This, {i32, I32}) -> | |
283 | write(This, {byte, I32}); | |
284 | ||
285 | write(This, {i64, I64}) -> | |
286 | write(This, {byte, I64}); | |
287 | ||
288 | write(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 | ||
294 | write(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() | |
306 | write(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 | ||
311 | write_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. | |
327 | read_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 | ||
336 | read_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. | |
351 | expect(#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 | ||
360 | expect(#json_protocol{jsx={event, Event, Next}}=State, ExpectedEvent) -> | |
361 | expect(State#json_protocol{jsx={event, {Event, none}, Next}}, ExpectedEvent). | |
362 | ||
363 | convert_data(integer, I) -> list_to_integer(I); | |
364 | convert_data(float, F) -> list_to_float(F); | |
365 | convert_data(_, D) -> D. | |
366 | ||
367 | expect_many(State, ExpectedList) -> | |
368 | expect_many_1(State, ExpectedList, [], ok). | |
369 | ||
370 | expect_many_1(State, [], ResultList, Status) -> | |
371 | {State, {Status, lists:reverse(ResultList)}}; | |
372 | expect_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 | |
382 | expect_nodata(This, ExpectedList) -> | |
383 | case expect_many(This, ExpectedList) of | |
384 | {State, {ok, _}} -> | |
385 | {State, ok}; | |
386 | Error -> | |
387 | Error | |
388 | end. | |
389 | ||
390 | read_field(#json_protocol{jsx={event, Field, [Next|Rest]}} = State) -> | |
391 | NewState = State#json_protocol{jsx={event, Next, Rest}}, | |
392 | {NewState, Field}. | |
393 | ||
394 | read(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 | ||
411 | read(This, message_end) -> | |
412 | expect_nodata(This, [end_array]); | |
413 | ||
414 | read(This, struct_begin) -> | |
415 | expect_nodata(This, [start_object]); | |
416 | ||
417 | read(This, struct_end) -> | |
418 | expect_nodata(This, [end_object]); | |
419 | ||
420 | read(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 | ||
440 | read(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}]}}] | |
444 | read(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 | ||
462 | read(This, map_end) -> | |
463 | expect_nodata(This, [end_object, end_array]); | |
464 | ||
465 | read(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 | ||
479 | read(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]}}] | |
483 | read(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 | ||
497 | read(This, set_end) -> | |
498 | expect_nodata(This, [end_array]); | |
499 | ||
500 | read(This0, field_stop) -> | |
501 | {This0, ok}; | |
502 | %% | |
503 | ||
504 | read(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 | ||
514 | read(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 | ||
526 | read(This0, i16) -> | |
527 | read(This0, byte); | |
528 | ||
529 | read(This0, i32) -> | |
530 | read(This0, byte); | |
531 | ||
532 | read(This0, i64) -> | |
533 | read(This0, byte); | |
534 | ||
535 | read(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 | |
546 | read(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()) | |
561 | new_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}. |