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}. 568