123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694moduleParam=structtype'at={name:stringoption;description:stringlist;typedef:'aRpc.Types.def;version:Rpc.Version.toption}typeboxed=Boxed:'at->boxedletmk?name?description?versiontypedef=letdescription=matchdescriptionwith|Somed->d|None->typedef.Rpc.Types.descriptionin{name;description;version;typedef}endmoduleError=structtype'at={def:'aRpc.Types.def;raiser:'a->exn;matcher:exn->'aoption}moduletypeERROR=sigtypetvalt:tRpc.Types.defvalinternal_error_of:exn->toptionendmoduleMake(T:ERROR)=structexceptionExnofT.tlet()=letprinter=function|Exnx->Some(Printf.sprintf"IDL Error: %s"(Rpcmarshal.marshalT.t.Rpc.Types.tyx|>Rpc.to_string))|_->NoneinPrintexc.register_printerprinterleterror={def=T.t;raiser=(function|e->Exne);matcher=(function|Exne->Somee|e->T.internal_error_ofe)}endendmoduleInterface=structtypedescription={name:string;namespace:stringoption;description:stringlist;version:Rpc.Version.t}endmoduletypeRPC=sigtypeimplementationtype'arestype('a,'b)comptype_fnvalimplement:Interface.description->implementationval(@->):'aParam.t->'bfn->('a->'b)fnvalnoargs:'bfn->(unit->'b)fnvalreturning:'aParam.t->'bError.t->('a,'b)compfnvaldeclare:string->stringlist->'afn->'aresvaldeclare_notification:string->stringlist->'afn->'aresendmoduletypeMONAD=sigtype'atvalreturn:'a->'atvalbind:'at->('a->'bt)->'btvalfail:exn->'atendexceptionMarshalErrorofstringexceptionUnknownMethodofstringexceptionUnboundImplementationofstringlistexceptionNoDescriptionletget_wire_namedescriptionname=matchdescriptionwith|None->name|Somed->(matchd.Interface.namespacewith|Somens->Printf.sprintf"%s.%s"nsname|None->name)letget_argcallhas_namednameis_opt=matchhas_named,name,call.Rpc.paramswith|true,Somen,Rpc.Dictnamed::unnamed->(matchList.partition(fun(x,_)->x=n)namedwith|(_,arg)::dups,otherswhenis_opt->Result.Ok(Rpc.Enum[arg],{callwithRpc.params=Rpc.Dict(dups@others)::unnamed})|(_,arg)::dups,others->Result.Ok(arg,{callwithRpc.params=Rpc.Dict(dups@others)::unnamed})|[],_otherswhenis_opt->Result.Ok(Rpc.Enum[],call)|_,_->Result.Error(`Msg(Printf.sprintf"Expecting named argument '%s'"n)))|true,None,Rpc.Dictnamed::unnamed->(matchunnamedwith|head::tail->Result.Ok(head,{callwithRpc.params=Rpc.Dictnamed::tail})|_->Result.Error(`Msg"Incorrect number of arguments"))|true,_,_->Result.Error(`Msg"Marshalling error: Expecting dict as first argument when named parameters exist")|false,None,head::tail->Result.Ok(head,{callwithRpc.params=tail})|false,None,[]->Result.Error(`Msg"Incorrect number of arguments")|false,Some_,_->failwith"Can't happen by construction"moduleMake(M:MONAD)=structmoduletypeRPCTRANSFORMER=sigtype'aboxtype('a,'b)resultb=('a,'b)Result.tboxtyperpcfn=Rpc.call->Rpc.responseM.tvallift:('a->'bM.t)->'a->'bboxvalbind:'abox->('a->'bM.t)->'bboxvalreturn:'a->'aboxvalget:'abox->'aM.tval(!@):'abox->'aM.tvalput:'aM.t->'aboxval(~@):'aM.t->'aboxendmoduleT=structtype'abox={box:'aM.t}type('a,'b)resultb=('a,'b)Result.tboxtyperpcfn=Rpc.call->Rpc.responseM.tletliftfx={box=fx}letbind{box=x}f={box=M.bindxf}letreturnx={box=M.returnx}letget{box=x}=xlet(!@)=getletputx={box=x}let(~@)=putendtypeclient_implementation=unittypeserver_implementation=(string,T.rpcfnoption)Hashtbl.tmoduleErrM:sigvalreturn:'a->('a,'b)T.resultbvalreturn_err:'b->('a,'b)T.resultbvalchecked_bind:('a,'b)T.resultb->('a->('c,'d)T.resultb)->('b->('c,'d)T.resultb)->('c,'d)T.resultbvalbind:('a,'b)T.resultb->('a->('c,'b)T.resultb)->('c,'b)T.resultbval(>>=):('a,'b)T.resultb->('a->('c,'b)T.resultb)->('c,'b)T.resultbend=structletreturnx=T.put(M.return(Result.Okx))letreturn_erre=T.put(M.return(Result.Errore))letchecked_bindxff1=T.bindxT.(function|Result.Okx->!@(fx)|Result.Errorx->!@(f1x))letbindxf=checked_bindxfreturn_errlet(>>=)xf=bindxfendmoduleGenClient()=structtypeimplementation=client_implementationtype'ares=T.rpcfn->'atype('a,'b)comp=('a,'b)T.resultbtype_fn=|Function:'aParam.t*'bfn->('a->'b)fn|NoArgsFunction:'bfn->(unit->'b)fn|Returning:('aParam.t*'bError.t)->('a,'b)compfnletdescription=refNoneletstrict=reffalseletmake_strict()=strict:=trueletimplementx=description:=Somex;()letreturningaerr=Returning(a,err)let(@->)tf=Function(t,f)letnoargsf=NoArgsFunctionfletdeclare_is_notificationname_ty(rpc:T.rpcfn)=letopenResultinletrecinner:typeb.(string*Rpc.t)listoption*Rpc.tlist->bfn->b=fun(named,unnamed)->function|Function(t,f)->letcur_named=matchnamedwith|Somel->l|None->[]infunv->(matcht.Param.namewith|Somen->(matcht.Param.typedef.Rpc.Types.ty,vwith|Rpc.Types.Optionty,Somev'->letmarshalled=Rpcmarshal.marshaltyv'ininner(Some((n,marshalled)::cur_named),unnamed)f|Rpc.Types.Option_ty,None->inner(Somecur_named,unnamed)f|ty,v->letmarshalled=Rpcmarshal.marshaltyvininner(Some((n,marshalled)::cur_named),unnamed)f)|None->letmarshalled=Rpcmarshal.marshalt.Param.typedef.Rpc.Types.tyvininner(named,marshalled::unnamed)f)|NoArgsFunctionf->fun()->inner(named,unnamed)f|Returning(t,e)->letwire_name=get_wire_name!descriptionnameinletargs=matchnamedwith|None->List.revunnamed|Somel->Rpc.Dictl::List.revunnamedinletcall'=Rpc.callwire_nameargsinletcall={call'withis_notification}inletrpc=T.put(rpccall)inletres=T.bindrpc(funr->ifr.Rpc.successthen(matchRpcmarshal.unmarshalt.Param.typedef.Rpc.Types.tyr.Rpc.contentswith|Okx->M.return(Okx)|Error(`Msgx)->M.fail(MarshalErrorx))else(matchRpcmarshal.unmarshale.Error.def.Rpc.Types.tyr.Rpc.contentswith|Okx->if!strictthenM.fail(e.Error.raiserx)elseM.return(Errorx)|Error(`Msgx)->M.fail(MarshalErrorx)))inresininner(None,[])tyletdeclare_notificationnameaty(rpc:T.rpcfn)=declare_truenameatyrpcletdeclarenameaty(rpc:T.rpcfn)=declare_falsenameatyrpcendletserverhashtbl=letimpl=Hashtbl.create(Hashtbl.lengthhashtbl)inletunbound_impls=Hashtbl.fold(funkeyfnacc->matchfnwith|None->key::acc|Somefn->Hashtbl.addimplkeyfn;acc)hashtbl[]inifunbound_impls<>[]thenraise(UnboundImplementationunbound_impls);funcall->letfn=tryHashtbl.findimplcall.Rpc.namewith|Not_found->raise(UnknownMethodcall.Rpc.name)infncallletcombinehashtbls=letresult=Hashtbl.create16inList.iter(Hashtbl.iter(funkv->Hashtbl.addresultkv))hashtbls;resultmoduleGenServer()=structtypeimplementation=server_implementationtype('a,'b)comp=('a,'b)T.resultbtype'ares='a->unittype_fn=|Function:'aParam.t*'bfn->('a->'b)fn|NoArgsFunction:'bfn->(unit->'b)fn|Returning:('aParam.t*'bError.t)->('a,'b)compfnletfuncs=Hashtbl.create20letdescription=refNoneletimplementx=description:=Somex;funcsletreturningab=Returning(a,b)let(@->)tf=Function(t,f)letnoargsf=NoArgsFunctionfletrechas_named_args:typea.afn->bool=function|Function(t,f)->(matcht.Param.namewith|Some_->true|None->has_named_argsf)|NoArgsFunctionf->has_named_argsf|Returning(_,_)->falseletdeclare_:bool->string->stringlist->'afn->'ares=funis_notificationname_ty->let(>>=)=M.bindin(* We do not know the wire name yet as the description may still be unset *)Hashtbl.addfuncsnameNone;funimpl->((* Sanity check: ensure the description has been set before we declare
any RPCs. Here we raise an exception immediately and let everything fail. *)match!descriptionwith|Some_->()|None->raiseNoDescription);letrpcfn=lethas_named=has_named_argstyinletrecinner:typea.afn->a->T.rpcfn=funfimplcall->matchfwith|Function(t,f)->letis_opt=matcht.Param.typedef.Rpc.Types.tywith|Rpc.Types.Option_->true|_->falsein(matchget_argcallhas_namedt.Param.nameis_optwith|Result.Ok(x,y)->M.return(x,y)|Result.Error(`Msgm)->M.fail(MarshalErrorm))>>=fun(arg_rpc,call')->letz=Rpcmarshal.unmarshalt.Param.typedef.Rpc.Types.tyarg_rpcin(matchzwith|Result.Okarg->innerf(implarg)call'|Result.Error(`Msgm)->M.fail(MarshalErrorm))|NoArgsFunctionf->innerf(impl())call|Returning(t,e)->T.bindimpl(function|Result.Okx->letres=Rpc.success(Rpcmarshal.marshalt.Param.typedef.Rpc.Types.tyx)inM.return{reswithis_notification}|Result.Errory->letres=Rpc.failure(Rpcmarshal.marshale.Error.def.Rpc.Types.tyy)inM.return{reswithis_notification})|>T.getininnertyimplinHashtbl.removefuncsname;(* The wire name might be different from the name *)letwire_name=get_wire_name!descriptionnameinHashtbl.addfuncswire_name(Somerpcfn)letdeclare_notificationnameaty=declare_truenameatyletdeclarenameaty=declare_falsenameatyendendmoduleExnM=structtype'at=|Vof'a|Eofexnletreturnx=Vxletliftfx=matchfxwith|y->Vy|exceptione->Eeletbindx(f:'a->'bt):'bt=matchxwith|Vx->fx|Ee->Eelet(>>=)=bindletfaile=Eeletrun=function|Vx->x|Ee->raiseeendmoduleIdM=structtype'at=Tof'aletreturnx=Txletliftfx=T(fx)letbind(Tx)f=fxlet(>>=)=bindletfaile=raiseeletrun(Tx)=xend(* A default error variant as an example. In real code, this is more easily expressed by using the PPX:
type default_error = InternalError of string [@@deriving rpcty]
*)moduleDefaultError=structtypet=InternalErrorofstringexceptionInternalErrorExnofstringletinternalerror:(string,t)Rpc.Types.tag=letopenRpc.Typesin{tname="InternalError";tdescription=["Internal Error"];tversion=Some(1,0,0);tcontents=BasicString;tpreview=(function|InternalErrors->Somes);treview=(funs->InternalErrors)}(* And then we can create the 'variant' type *)lett:tRpc.Types.variant=letopenRpc.Typesin{vname="t";variants=[BoxedTaginternalerror];vversion=Some(1,0,0);vdefault=Some(InternalError"Unknown error tag!");vconstructor=(funst->matchswith|"InternalError"->Rresult.R.map(funs->internalerror.treviews)(t.tget(BasicString))|s->Rresult.R.error_msg(Printf.sprintf"Unknown tag '%s'"s))}letdef=letopenRpc.Typesin{name="default_error";description=["Errors declared as part of the interface"];ty=Variantt}leterr=letopenErrorin{def;raiser=(function|InternalErrors->raise(InternalErrorExns));matcher=(function|InternalErrorExns->Some(InternalErrors)|_->None)}endmoduleExn=structtyperpcfn=Rpc.call->Rpc.responsetypeclient_implementation=unittypeserver_implementation=(string,rpcfnoption)Hashtbl.tmoduleGenClient(R:sigvalrpc:rpcfnend)=structtypeimplementation=client_implementationtype('a,'b)comp='atype'ares='atype_fn=|Function:'aParam.t*'bfn->('a->'b)fn|NoArgsFunction:'bfn->(unit->'b)fn|Returning:('aParam.t*'bError.t)->('a,_)compfnletdescription=refNoneletimplementx=description:=Somex;()letreturningaerr=Returning(a,err)let(@->)tf=Function(t,f)letnoargsf=NoArgsFunctionfletdeclare_is_notificationname_ty=letopenResultinletrecinner:typeb.(string*Rpc.t)listoption*Rpc.tlist->bfn->b=fun(named,unnamed)->function|Function(t,f)->letcur_named=matchnamedwith|Somel->l|None->[]infunv->(matcht.Param.namewith|Somen->(matcht.Param.typedef.Rpc.Types.ty,vwith|Rpc.Types.Optionty,Somev'->letmarshalled=Rpcmarshal.marshaltyv'ininner(Some((n,marshalled)::cur_named),unnamed)f|Rpc.Types.Option_ty,None->inner(Somecur_named,unnamed)f|ty,v->letmarshalled=Rpcmarshal.marshaltyvininner(Some((n,marshalled)::cur_named),unnamed)f)|None->letmarshalled=Rpcmarshal.marshalt.Param.typedef.Rpc.Types.tyvininner(named,marshalled::unnamed)f)|NoArgsFunctionf->fun()->inner(named,unnamed)f|Returning(t,e)->letwire_name=get_wire_name!descriptionnameinletargs=matchnamedwith|None->List.revunnamed|Somel->Rpc.Dictl::List.revunnamedinletcall'=Rpc.callwire_nameargsinletcall={call'withis_notification}inletr=R.rpccallinifr.Rpc.successthen(matchRpcmarshal.unmarshalt.Param.typedef.Rpc.Types.tyr.Rpc.contentswith|Okx->x|Error(`Msgx)->raise(MarshalErrorx))else(matchRpcmarshal.unmarshale.Error.def.Rpc.Types.tyr.Rpc.contentswith|Okx->raise(e.Error.raiserx)|Error(`Msgx)->raise(MarshalErrorx))ininner(None,[])tyletdeclarenameaty=declare_falsenameatyletdeclare_notificationnameaty=declare_truenameatyendletserverhashtbl=letimpl=Hashtbl.create(Hashtbl.lengthhashtbl)inletunbound_impls=Hashtbl.fold(funkeyfnacc->matchfnwith|None->key::acc|Somefn->Hashtbl.addimplkeyfn;acc)hashtbl[]inifunbound_impls<>[]thenraise(UnboundImplementationunbound_impls);funcall->letfn=tryHashtbl.findimplcall.Rpc.namewith|Not_found->raise(UnknownMethodcall.Rpc.name)infncallletcombinehashtbls=letresult=Hashtbl.create16inList.iter(Hashtbl.iter(funkv->Hashtbl.addresultkv))hashtbls;resultmoduleGenServer()=structtypeimplementation=server_implementationtype('a,'b)comp='atype'ares='a->unittype_fn=|Function:'aParam.t*'bfn->('a->'b)fn|NoArgsFunction:'bfn->(unit->'b)fn|Returning:('aParam.t*'bError.t)->('a,_)compfnletfuncs=Hashtbl.create20letdescription=refNoneletimplementx=description:=Somex;funcsletreturningab=Returning(a,b)let(@->)tf=Function(t,f)letnoargsf=NoArgsFunctionftypeboxed_error=BoxedError:'aError.t->boxed_errorletrecget_error_ty:typea.afn->boxed_error=function|Function(_,f)->get_error_tyf|NoArgsFunctionf->get_error_tyf|Returning(_,e)->BoxedErroreletrechas_named_args:typea.afn->bool=function|Function(t,f)->(matcht.Param.namewith|Some_->true|None->has_named_argsf)|NoArgsFunctionf->has_named_argsf|Returning(_,_)->falseletdeclare_:bool->string->stringlist->'afn->'ares=funis_notificationname_ty->(* We do not know the wire name yet as the description may still be unset *)Hashtbl.addfuncsnameNone;funimpl->((* Sanity check: ensure the description has been set before we declare
any RPCs *)match!descriptionwith|Some_->()|None->raiseNoDescription);letrpcfn=lethas_named=has_named_argstyinletrecinner:typea.afn->a->Rpc.call->Rpc.response=funfimplcall->trymatchfwith|Function(t,f)->letis_opt=matcht.Param.typedef.Rpc.Types.tywith|Rpc.Types.Option_->true|_->falseinletarg_rpc,call'=matchget_argcallhas_namedt.Param.nameis_optwith|Result.Ok(x,y)->x,y|Result.Error(`Msgm)->raise(MarshalErrorm)inletz=Rpcmarshal.unmarshalt.Param.typedef.Rpc.Types.tyarg_rpcinletarg=matchzwith|Result.Okarg->arg|Result.Error(`Msgm)->raise(MarshalErrorm)ininnerf(implarg)call'|NoArgsFunctionf->innerf(impl())call|Returning(t,_)->letcall=Rpc.success(Rpcmarshal.marshalt.Param.typedef.Rpc.Types.tyimpl)in{callwithis_notification}with|e->let(BoxedErrorerror_ty)=get_error_tyfin(matcherror_ty.Error.matcherewith|Somey->Rpc.failure(Rpcmarshal.marshalerror_ty.Error.def.Rpc.Types.tyy)|None->raisee)ininnertyimplinHashtbl.removefuncsname;(* The wire name might be different from the name *)letwire_name=get_wire_name!descriptionnameinHashtbl.addfuncswire_name(Somerpcfn)letdeclarenameaty=declare_truenameatyletdeclare_notificationnameaty=declare_falsenameatyendend