123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302(*
* Copyright (c) 2006-2009 Citrix Systems Inc.
* Copyright (c) 2006-2014 Thomas Gazagnaire <thomas@gazagnaire.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*)openRpcmoduleYojson_private=structincludeYojson.Safeletfrom_string?(strict=true)?buf?fname?lnums=letopenYojsonintryletlexbuf=Lexing.from_stringsinletv=init_lexer?buf?fname?lnum()inifstrictthenfrom_lexbufvlexbufelsefrom_lexbufv~stream:truelexbufwith|End_of_input->json_error"Blank input data"endmoduleY=Yojson_privatemoduleU=Yojson.Basic.Utiltypeversion=|V1|V2letrecrpc_to_jsont=matchtwith|Inti->`Intlit(Int64.to_stringi)|Int32i->`Int(Int32.to_inti)|Boolb->`Boolb|Floatr->`Floatr|Strings->`Strings|DateTimed->`Stringd|Base64b->`Stringb|Null->`Null|Enuma->`List(Rpcmarshal.tailrec_maprpc_to_jsona)|Dicta->`Assoc(Rpcmarshal.tailrec_map(fun(k,v)->k,rpc_to_jsonv)a)exceptionJsonToRpcErrorofY.tletrecjson_to_rpct=matchtwith|`Intliti->Int(Int64.of_stringi)|`Inti->Int(Int64.of_inti)|`Boolb->Boolb|`Floatr->Floatr|`Strings->(* TODO: check if it is a DateTime *)Strings(* | DateTime d -> `String d *)(* | Base64 b -> `String b *)|`Null->Null|`Lista->Enum(Rpcmarshal.tailrec_mapjson_to_rpca)|`Assoca->Dict(Rpcmarshal.tailrec_map(fun(k,v)->k,json_to_rpcv)a)|unsupported->raise(JsonToRpcErrorunsupported)letto_fcttf=rpc_to_jsont|>Y.to_string|>fletto_buffertbuf=to_fctt(funs->Buffer.add_stringbufs)letto_stringt=rpc_to_jsont|>Y.to_stringletto_a~empty~appendt=letbuf=empty()into_fctt(funs->appendbufs);bufletnew_id=letcount=ref0Linfun()->count:=Int64.add1L!count;!countletstring_of_call?(version=V1)call=letjson=matchversionwith|V1->["method",Stringcall.name;"params",Enumcall.params]|V2->letparams=matchcall.paramswith|[Dictx]->Dictx|_->Enumcall.paramsin["jsonrpc",String"2.0";"method",Stringcall.name;"params",params]inletjson=ifnotcall.is_notificationthenjson@["id",Int(new_id())]elsejsoninto_string(Dictjson)letjson_of_response?(id=Int0L)versionresponse=ifresponse.Rpc.successthen(matchversionwith|V1->Dict["result",response.Rpc.contents;"error",Null;"id",id]|V2->Dict["jsonrpc",String"2.0";"result",response.Rpc.contents;"id",id])else(matchversionwith|V1->Dict["result",Null;"error",response.Rpc.contents;"id",id]|V2->Dict["jsonrpc",String"2.0";"error",response.Rpc.contents;"id",id])letjson_of_error_object?(data=None)codemessage=letdata_json=matchdatawith|Somed->["data",d]|None->[]inDict(["code",Intcode;"message",Stringmessage]@data_json)letstring_of_response?(id=Int0L)?(version=V1)response=letjson=json_of_response~idversionresponseinto_stringjsonleta_of_response?(id=Int0L)?(version=V1)~empty~appendresponse=letjson=json_of_response~idversionresponseinto_a~empty~appendjsonletof_string?(strict=true)s=s|>Y.from_string~strict|>json_to_rpcletof_a~next_charb=letbuf=Buffer.create2048inletrecacc()=matchnext_charbwith|Somec->Buffer.add_charbufc;acc()|None->()inacc();Buffer.contentsbuf|>of_stringletget'namedict=trySome(List.assocnamedict)with|Not_found->NoneexceptionMalformed_method_requestofstringexceptionMalformed_method_responseofstringexceptionMissing_fieldofstringletgetnamedict=matchget'namedictwith|None->ifRpc.get_debug()thenPrintf.eprintf"%s was not found in the dictionary\n"name;raise(Missing_fieldname)|Somev->vletversion_id_and_call_of_string_optionstr=trymatchof_stringstrwith|Dictd->letname=matchget"method"dwith|Strings->s|_->raise(Malformed_method_request"Invalid field 'method' in request body")inletversion=matchget'"jsonrpc"dwith|None->V1|Some(String"2.0")->V2|_->raise(Malformed_method_request"Invalid field 'jsonrpc' in request body")inletparams=matchversionwith|V1->(matchget"params"dwith|Enuml->l|_->raise(Malformed_method_request"Invalid field 'params' in request body"))|V2->(matchget'"params"dwith|None|SomeNull->[]|Some(Enuml)->l|Some(Dictl)->[Dictl]|_->raise(Malformed_method_request"Invalid field 'params' in request body"))inletid=matchget'"id"dwith|None|SomeNull->None(* is a notification *)|Some(Inta)->Some(Inta)|Some(Stringa)->Some(Stringa)|Some_->raise(Malformed_method_request"Invalid field 'id' in request body")inletc=callnameparamsinversion,id,{cwithis_notification=id==None}|_->raise(Malformed_method_request"Invalid request body")with|Missing_fieldfield->raise(Malformed_method_request(Printf.sprintf"Required field %s is missing"field))|JsonToRpcErrorjson->raise(Malformed_method_request(Printf.sprintf"Unable to parse %s"(Y.to_stringjson)))letversion_id_and_call_of_strings=letversion,id_,call=version_id_and_call_of_string_optionsinmatchid_with|Someid->version,id,call|None->raise(Malformed_method_request"Invalid field 'id' in request body")letcall_of_stringstr=let_,_,call=version_id_and_call_of_stringstrincall(* This functions parses the json and tries to extract a valid jsonrpc response
* (See http://www.jsonrpc.org/ for the exact specs). *)letget_responseextractorstr=trymatchextractorstrwith|Dictd->let_=matchget"id"dwith|Int_asx->x|String_asy->y|_->raise(Malformed_method_response"id")in(matchget'"jsonrpc"dwith|None->letresult=get"result"dinleterror=get"error"din(matchresult,errorwith|v,Null->successv|Null,v->failurev|x,y->raise(Malformed_method_response(Printf.sprintf"<result=%s><error=%s>"(Rpc.to_stringx)(Rpc.to_stringy))))|Some(String"2.0")->letresult=get'"result"dinleterror=get'"error"din(matchresult,errorwith|Somev,None->successv|None,Somev->(matchvwith|Dicterr->let(_:int64)=matchget"code"errwith|Inti->i|_->raise(Malformed_method_response"Error code")inlet_=matchget"message"errwith|Strings->s|_->raise(Malformed_method_response"Error message")infailurev|_->raise(Malformed_method_response"Error object"))|Somex,Somey->raise(Malformed_method_response(Printf.sprintf"<result=%s><error=%s>"(Rpc.to_stringx)(Rpc.to_stringy)))|None,None->raise(Malformed_method_response(Printf.sprintf"neither <result> nor <error> was found")))|_->raise(Malformed_method_response"jsonrpc"))|rpc->raise(Malformed_method_response(Printf.sprintf"<response_of_stream(%s)>"(to_stringrpc)))with|Missing_fieldfield->raise(Malformed_method_response(Printf.sprintf"<%s was not found>"field))|JsonToRpcErrorjson->raise(Malformed_method_response(Printf.sprintf"<unable to parse %s>"(Y.to_stringjson)))letresponse_of_string?(strict=true)str=get_response(of_string~strict)strletresponse_of_in_channelchannel=letof_channels=s|>Y.from_channel|>json_to_rpcinget_responseof_channelchannel