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