123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855(*****************************************************************************)(* *)(* Open Source License *)(* Copyright (C) 2016, OCamlPro. *)(* Copyright (c) 2020-2021 Nomadic Labs <contact@nomadic-labs.com> *)(* *)(* Permission is hereby granted, free of charge, to any person obtaining a *)(* copy of this software and associated documentation files (the "Software"),*)(* to deal in the Software without restriction, including without limitation *)(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *)(* and/or sell copies of the Software, and to permit persons to whom the *)(* Software is furnished to do so, subject to the following conditions: *)(* *)(* The above copyright notice and this permission notice shall be included *)(* in all copies or substantial portions of the Software. *)(* *)(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*)(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *)(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *)(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*)(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *)(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *)(* DEALINGS IN THE SOFTWARE. *)(* *)(*****************************************************************************)openRestoletmap_optionf=functionNone->None|Somex->Some(fx)let(>>=)=Lwt.bindlet(>|=)=Lwt.(>|=)moduleAnswer=struct(** Return type for service handler *)type('o,'e)t=[`Okof'o(* 200 *)|`OkChunkof'o(* 200 *)|`OkStreamof'ostream(* 200 *)|`Createdofstringoption(* 201 *)|`No_content(* 204 *)|`Unauthorizedof'eoption(* 401 *)|`Forbiddenof'eoption(* 403 *)|`Not_foundof'eoption(* 404 *)|`Conflictof'eoption(* 409 *)|`Goneof'eoption(* 410 *)|`Errorof'eoption(* 500 *)]and'astream={next:unit->'aoptionLwt.t;shutdown:unit->unit}letreturnx=Lwt.return(`Okx)letreturn_streamx=Lwt.return(`OkStreamx)endmoduleMake(Encoding:ENCODING)=structmoduleService=Resto.MakeService(Encoding)moduleCurry=structtype(_,_,_,_,_,_)conv=|Z:(unit,'g,'g,unit,'f,'f)conv|S:('t,'g,'b*'s,'rt,'f,'r)conv->('t*'b,'g,'s,'a*'rt,'a->'f,'r)convletreverse:typeacdef.(a,c,unit,d,e,f)conv->a->c=funcv->letrecreverse:typeacdefg.(a,c,d,e,f,g)conv->a->d->c=funcvacc->match(c,v)with|(Z,_)->acc|(Sc,(v,x))->reversecv(x,acc)inreversecv()letreccurry:typeabcdef.(a,b,c,d,e,f)conv->e->d->f=funcf->matchcwithZ->fun()->f|Sc->fun(v,x)->curryc(fv)xletcurrycf=letf=currycfinfunx->f(reversecx)endtypestep=|Staticofstring|DynamicofArg.descr|DynamicTailofArg.descrtypeconflict=|CServiceofmeth|CDir|CBuilder|CTail|CTypesofArg.descr*Arg.descr|CTypeofArg.descr*stringlistexceptionConflictofsteplist*conflictopenResto.Internaltypelookup_error=[`Not_found(* 404 *)|`Method_not_allowedofmethlist(* 405 *)|`Cannot_parse_pathofstringlist*Arg.descr*string(* 400 *)]type('query,'input,'output,'error)types=('query,'input,'output,'error)Service.Internal.types={query:'queryResto.Query.t;input:'inputService.input;output:'outputEncoding.t;error:'errorEncoding.t;}type'keyt=|Empty:'keyt|Static:'keystatic_directory->'keyt|Dynamic:stringoption*('key->'keydirectoryLwt.t)->'keyt|DynamicTail:'aarg*('key*'alist)t->'keytand'keydirectory='keytand'keystatic_directory={services:'keyregistered_service_builderMethMap.t;subdirs:'keystatic_subdirectoriesoption;}and_static_subdirectories=|Suffixes:'keydirectoryStringMap.t->'keystatic_subdirectories|Arg:'aResto.Internal.arg*('key*'a)directory->'keystatic_subdirectoriesandregistered_service=|Service:{types:('q,'i,'o,'e)types;handler:'q->'i->('o,'e)Answer.tLwt.t;}->registered_serviceand'keyregistered_service_builder={meth:Resto.meth;description:Encoding.schemaDescription.service;builder:'key->registered_serviceLwt.t;}letempty=Emptyletrecmap_directory:typeab.(a->bLwt.t)->bdirectory->adirectory=funft->matchtwith|Empty->Empty|Dynamic(descr,builder)->letbuildera=fa>>=builder>|=map_directoryfinDynamic(descr,builder)|DynamicTail(arg,dir)->DynamicTail(arg,map_directory(fun(x,l)->fx>|=funx->(x,l))dir)|Staticdir->Static(map_static_directoryfdir)andmap_static_directory:typeab.(a->bLwt.t)->bstatic_directory->astatic_directory=funft->{services=MethMap.map(map_registered_servicef)t.services;subdirs=map_option(map_static_subdirectoriesf)t.subdirs;}andmap_static_subdirectories:typeab.(a->bLwt.t)->bstatic_subdirectories->astatic_subdirectories=funft->matchtwith|Suffixesmap->Suffixes(StringMap.map(map_directoryf)map)|Arg(arg,dir)->letdir=map_directory(fun(a,x)->fa>|=funa->(a,x))dirinArg(arg,dir)andmap_registered_service:typeab.(a->bLwt.t)->bregistered_service_builder->aregistered_service_builder=funfrs->{rswithbuilder=(funp->fp>>=funp->rs.builderp)}letmap=map_directoryletprefix:typeppr.(pr,p)Path.path->pdirectory->prdirectory=funpathdir->letrecprefix:typekpr.(pr,k)Resto.Internal.path->kdirectory->prdirectory=funpathdir->matchpathwith|Root->dir|Static(path,name)->letsubdirs=Suffixes(StringMap.singletonnamedir)inprefixpath(Static{subdirs=Somesubdirs;services=MethMap.empty})|Dynamic(path,arg)->letsubdirs=Arg(arg,dir)inprefixpath(Static{subdirs=Somesubdirs;services=MethMap.empty})|DynamicTail_->invalid_arg"RestoDirectory.prefix"inprefix(Resto.Internal.to_pathpath)dirletconflictstepskind=raise(Conflict(steps,kind))letrecmerge:typep.steplist->pdirectory->pdirectory->pdirectory=funpatht1t2->match(t1,t2)with|(Empty,t)->t|(t,Empty)->t|(Staticn1,Staticn2)->Static(merge_static_directorypathn1n2)|(Dynamic_,_)|(_,Dynamic_)->conflictpathCBuilder|(DynamicTail_,_)|(_,DynamicTail_)->conflictpathCTailandmerge_static_directory:typep.steplist->pstatic_directory->pstatic_directory->pstatic_directory=funpatht1t2->letsubdirs=match(t1.subdirs,t2.subdirs)with|(None,None)->None|(None,Somedir)|(Somedir,None)->Somedir|(Somed1,Somed2)->(match(d1,d2)with|(Suffixesm1,Suffixesm2)->letmerge=StringMap.fold(funntm->letst=tryStringMap.findnmwithNot_found->emptyinStringMap.addn(merge(Staticn::path)stt)m)inSome(Suffixes(mergem1m2))|(Arg(arg1,subt1),Arg(arg2,subt2))->(tryletEq=Ty.eqarg1.idarg2.idinletsubt=merge(Dynamicarg1.descr::path)subt1subt2inSome(Arg(arg1,subt))withTy.Not_equal->conflictpath(CTypes(arg1.descr,arg2.descr)))|(Arg(arg,_),Suffixesm)->conflictpath(CType(arg.descr,List.mapfst(StringMap.bindingsm)))|(Suffixesm,Arg(arg,_))->conflictpath(CType(arg.descr,List.mapfst(StringMap.bindingsm))))inletservices=MethMap.fold(funmethsmap->ifMethMap.memmethmapthenconflictpath(CServicemeth)elseMethMap.addmethsmap)t1.servicest2.servicesin{subdirs;services}letmergexy=merge[]xyletrecdescribe_directory:typea.recurse:bool->?arg:a->adirectory->Encoding.schemaDescription.directoryLwt.t=fun~recurse?argdir->matchdirwith|Empty->Lwt.returnDescription.Empty|Dynamic(descr,builder)->(matchargwith|None->Lwt.return(Dynamicdescr:Encoding.schemaDescription.directory)|Somearg->builderarg>>=fundir->describe_directory~recursedir)|DynamicTail(_,dir)->describe_directory~recursedir|Staticdir->describe_static_directoryrecursedir>>=fundir->Lwt.return(Staticdir:Encoding.schemaDescription.directory)anddescribe_static_directory:typea.bool->astatic_directory->Encoding.schemaDescription.static_directoryLwt.t=funrecursedir->letservices=MethMap.mapdescribe_servicedir.servicesin(ifrecursethenmatchdir.subdirswith|None->Lwt.return_none|Somesubdirs->describe_static_subdirectoriessubdirs>>=fundirs->Lwt.return(Somedirs)elseLwt.return_none)>>=funsubdirs->Lwt.return({services;subdirs}:Encoding.schemaDescription.static_directory)anddescribe_static_subdirectories:typea.astatic_subdirectories->Encoding.schemaDescription.static_subdirectoriesLwt.t=fundir->matchdirwith|Suffixesmap->StringMap.fold(funkeydirmap->map>>=funmap->describe_directory~recurse:truedir>>=fundir->Lwt.return(StringMap.addkeydirmap))map(Lwt.returnStringMap.empty)>>=funmap->Lwt.return(Suffixesmap:Encoding.schemaDescription.static_subdirectories)|Arg(arg,dir)->describe_directory~recurse:truedir>>=fundir->Lwt.return(Arg(arg.descr,dir):Encoding.schemaDescription.static_subdirectories)anddescribe_service:typea.aregistered_service_builder->Encoding.schemaDescription.service=fun{description;_}->descriptionanddescribe_query:typea.aResto.Internal.query->Description.query_itemlist=fun(Fields(fields,_))->letrecloop:typeab.(a,b)query_fields->_=function|F0->[]|F1(f,fs)->{Description.name=field_namef;description=field_descriptionf;kind=field_kindf;}::loopfsinloopfields(****************************************************************************
* Lookup
****************************************************************************)typeresolved_directory=|Dir:'astatic_directory*'a->resolved_directoryletrecresolve:typea.stringlist->adirectory->a->stringlist->(resolved_directory,_)resultLwt.t=funprefixdirargspath->match(path,dir)with|(_,Empty)->Lwt.return_error`Not_found|(path,Dynamic(_,builder))->builderargs>>=fundir->resolveprefixdirargspath|(path,DynamicTail(arg,dir))->(matchList.fold_right(funeacc->matchaccwith|Error_aserr->err|Ok(prefix,path)->(matcharg.destructewith|Oks->Ok(e::prefix,s::path)|Errormsg->Error(`Cannot_parse_path(List.rev(e::prefix),arg.descr,msg))))path(Ok(prefix,[]))with|Ok(prefix,path)->resolveprefixdir(args,path)[]|Error_aserr->Lwt.returnerr)|([],Staticsdir)->Lwt.return_ok(Dir(sdir,args))|(_name::_path,Static{subdirs=None;_})->Lwt.return_error`Not_found|(name::path,Static{subdirs=Some(Suffixesstatic);_})->(matchStringMap.findnamestaticwith|exceptionNot_found->Lwt.return_error`Not_found|dir->resolve(name::prefix)dirargspath)|(name::path,Static{subdirs=Some(Arg(arg,dir));_})->(matcharg.destructnamewith|Okx->resolve(name::prefix)dir(args,x)path|Errormsg->Lwt.return_error@@`Cannot_parse_path(List.rev(name::prefix),arg.descr,msg))letlookup:typea.adirectory->a->meth->stringlist->(registered_service,lookup_error)resultLwt.t=fundirargsmethpath->resolve[]dirargspath>>=function|Error_aserr->Lwt.returnerr|Ok(Dir(dir,args))->(matchMethMap.findmethdir.serviceswith|exceptionNot_found->(matchMethMap.bindingsdir.serviceswith|[]->Lwt.return_error`Not_found|l->Lwt.return_error(`Method_not_allowed(List.mapfstl)))|rs->rs.builderargs>>=Lwt.return_ok)letlookup=(lookup:_->_->_->_->(_,lookup_error)resultLwt.t:>_->_->_->_->(_,[>lookup_error])resultLwt.t)letallowed_methods:typea.adirectory->a->stringlist->(Resto.methlist,lookup_error)resultLwt.t=fundirargspath->resolve[]dirargspath>>=function|Errorerr->Lwt.return_errorerr|Ok(Dir(dir,_))->(matchMethMap.bindingsdir.serviceswith|[]->Lwt.return_error`Not_found|l->Lwt.return_ok(List.mapfstl))letallowed_methods=(allowed_methods:_->_->_->(_,lookup_error)resultLwt.t:>_->_->_->(_,[>lookup_error])resultLwt.t)letrecbuild_dynamic_dir:typep.pdirectory->p->pdirectoryLwt.t=fundirargs->matchdirwith|Dynamic(_,builder)->builderargs>>=fundir->build_dynamic_dirdirargs|_->Lwt.returndirletrectransparent_resolve:typeprp.prdirectory->(pr,p)path->p->pdirectoryoptionLwt.t=fundirpathrargs->matchpathwith|Root->Lwt.return_somedir|Static(path,name)->(transparent_resolvedirpathrargs>>=function|None->Lwt.return_none|Somedir->(build_dynamic_dirdirrargs>>=function|Dynamic(_,_)->assertfalse(* should not happen. *)|Static{subdirs=Some(Suffixess);_}->Lwt.return_some(StringMap.findnames)|Empty->Lwt.return_none|Static_->Lwt.return_none|DynamicTail_->Lwt.return_none))|Dynamic(ipath,iarg)->(transparent_resolvediripath(fstrargs)>>=function|None->Lwt.return_none|Somedir->(build_dynamic_dirdir(fstrargs)>>=function|Dynamic(_,_)->assertfalse(* should not happen. *)|Static{subdirs=Some(Arg(arg,dir));_}->(matchTy.eqiarg.idarg.idwith|exceptionTy.Not_equal->Lwt.return_none|Eq->Lwt.return_some(dir:(_*_)directory:>pdirectory))|Empty->Lwt.return_none|Static_->Lwt.return_none|DynamicTail_->Lwt.return_none))|DynamicTail(path,arg)->(transparent_resolvedirpath(fstrargs)>>=function|None->Lwt.return_none|Somedir->(build_dynamic_dirdir(fstrargs)>>=function|Dynamic(_,_)->assertfalse(* should not happen. *)|DynamicTail(iarg,dir)->(matchTy.eqiarg.idarg.idwith|exceptionTy.Not_equal->Lwt.return_none|Eq->Lwt.return_some(dir:(_*_)directory:>pdirectory))|Empty->Lwt.return_none|Static_->Lwt.return_none))lettransparent_lookup:typeprefixparamsqueryinputoutputerror.prefixdirectory->(_,prefix,params,query,input,output,error)Service.t->params->query->input->(output,error)Answer.tLwt.t=fundirserviceparamsquerybody->letservice=Service.Internal.to_serviceserviceintransparent_resolvedirservice.pathparams>>=function|None->Lwt.return(`Not_foundNone)|Some(Static{services;_})->(try(MethMap.findservice.methservices).builderparams>>=fun(Service{handler;types})->matchService.Internal.eqtypesservice.typeswith|exceptionService.Internal.Not_equal->Lwt.return(`Not_foundNone)|Service.Internal.Eq->(handlerquerybody:(_,_)Answer.tLwt.t:>(output,error)Answer.tLwt.t)withNot_found->Lwt.return(`Not_foundNone))|Some_->Lwt.return(`Not_foundNone)lettransparent_lookup=(transparent_lookup:_->(Resto.meth,_,_,_,_,_,_)Service.t->_->_->_->(_,_)Answer.tLwt.t:>_->([<Resto.meth],_,_,_,_,_,_)Service.t->_->_->_->[>(_,_)Answer.t]Lwt.t)letrecdescribe_rpath:typeab.Description.path_itemlist->(a,b)path->Description.path_itemlist=funaccpath->matchpathwith|Root->acc|Static(rpath,name)->describe_rpath(PStaticname::acc)rpath|Dynamic(rpath,arg)->describe_rpath(PDynamicarg.descr::acc)rpath|DynamicTail(rpath,arg)->describe_rpath(PDynamicTailarg.descr::acc)rpath(****************************************************************************
* Registration
****************************************************************************)letrecstep_of_path:typeprk.(rk,p)path->steplist->steplist=funpathacc->matchpathwith|Root->acc|Static(path,name)->step_of_pathpath(Staticname::acc)|Dynamic(path,arg)->step_of_pathpath(Dynamicarg.descr::acc)|DynamicTail(path,arg)->step_of_pathpath(DynamicTailarg.descr::acc)letstep_of_pathp=step_of_pathp[]letconflictpathkind=raise(Conflict(step_of_pathpath,kind))letrecinsert:typekrk.(rk,k)path->rkdirectory->kdirectory*(kdirectory->rkdirectory)=funpathdir->matchpathwith|Root->(dir,funx->x)|Static(subpath,name)->let(subdir,rebuild)=insertsubpathdirinlet(dirmap,services)=matchsubdirwith|Empty->(StringMap.empty,MethMap.empty)|Static{subdirs=None;services}->(StringMap.empty,services)|Static{subdirs=Some(Suffixesm);services}->(m,services)|Static{subdirs=Some(Arg(arg,_));_}->conflictpath(CType(arg.descr,[name]))|Dynamic_->conflictpathCBuilder|DynamicTail_->conflictpathCTailinletdir=tryStringMap.findnamedirmapwithNot_found->emptyinletrebuilds=letsubdirs=Some(Suffixes(StringMap.addnamesdirmap))inrebuild(Static{subdirs;services})in(dir,rebuild)|Dynamic(subpath,arg)->let(subdir,rebuild)=insertsubpathdirinlet(dir,services)=matchsubdirwith|Empty->(Empty,MethMap.empty)|Static{subdirs=None;services}->(Empty,services)|Static{subdirs=Some(Arg(arg',dir));services}->(tryletEq=Ty.eqarg.idarg'.idin((dir:>kdirectory),services)withTy.Not_equal->conflictpath(CTypes(arg.descr,arg'.descr)))|Static{subdirs=Some(Suffixesm);_}->conflictpath(CType(arg.descr,List.mapfst(StringMap.bindingsm)))|Dynamic_->conflictpathCBuilder|DynamicTail_->conflictpathCTailinletrebuilds=letsubdirs=Some(Arg(arg,s))inrebuild(Static{subdirs;services})in(dir,rebuild)|DynamicTail(subpath,arg)->(let(subdir,rebuild)=insertsubpathdirinmatchsubdirwith|Empty->letrebuilds=rebuild(DynamicTail(arg,s))in(empty,rebuild)|Static{subdirs=None;services}->conflictpath(CService(fst(MethMap.min_bindingservices)))|Static{subdirs=Some(Arg(arg,_));_}->conflictpath(CType(arg.descr,[]))|Static{subdirs=Some(Suffixesm);_}->conflictpath(CType(arg.descr,List.mapfst(StringMap.bindingsm)))|Dynamic_->conflictpathCBuilder|DynamicTail_->conflictpathCTail)letregister:typepqioepr.prdirectory->(_,pr,p,q,i,o,e)Service.t->(p->q->i->(o,e)Answer.tLwt.t)->prdirectory=funrootshandler->lets=Service.Internal.to_servicesinletregister:typek.(pr,k)path->(k->q->i->(o,e)Answer.tLwt.t)->prdirectory=funpathhandler->let(dir,insert)=insertpathrootinletrs=letdescription:_Description.service={meth=s.meth;path=describe_rpath[]path;description=s.description;query=describe_query(Resto.Internal.to_querys.types.query);input=(matchs.types.inputwith|Service.No_input->None|Service.Inputinput->Some(lazy(Encoding.schemainput)));output=lazy(Encoding.schemas.types.output);error=lazy(Encoding.schemas.types.error);}inletbuilderkey=Lwt.return(Service{types=s.types;handler=handlerkey})in{meth=s.meth;description;builder}inmatchdirwith|Empty->insert(Static{services=MethMap.singletons.methrs;subdirs=None})|Static({services;_}asdir)whennot(MethMap.mems.methservices)->insert(Static{dirwithservices=MethMap.adds.methrsservices})|Static_->conflictpath(CServices.meth)|Dynamic_->conflictpathCBuilder|DynamicTail_->conflictpathCTailinregisters.pathhandlerletregister=(register:_->(Resto.meth,_,_,_,_,_,_)Service.t->(_->_->_->(_,_)Answer.tLwt.t)->_:>_->([<Resto.meth],_,_,_,_,_,_)Service.t->(_->_->_->[<(_,_)Answer.t]Lwt.t)->_)letregister_dynamic_directory:typeprapr.?descr:string->prdirectory->(pr,a)Path.path->(a->adirectoryLwt.t)->prdirectory=fun?descrrootpathbuilder->letpath=Resto.Internal.to_pathpathinletregister:typek.(pr,k)path->(k->kdirectoryLwt.t)->prdirectory=funpathbuilder->let(dir,insert)=insertpathrootinmatchdirwith|Empty->insert(Dynamic(descr,builder))|Static{services;subdirs=None}->conflictpath(CService(fst(MethMap.chooseservices)))|Static{subdirs=Some_;_}->conflictpathCDir|Dynamic_->conflictpathCBuilder|DynamicTail_->conflictpathCTailinregisterpathbuilderletregister_describe_directory_service:typepr.prdirectory->(pr,pr,_)Service.description_service->prdirectory=funrootservice->letdir=refrootinletlookup(args,path){Description.recurse}()=resolve[]rootargspath>>=function|Error`Not_found|Error(`Cannot_parse_path_)->Lwt.return(`Not_foundNone)|Ok(Dir(dir,arg))->(describe_directory~recurse~arg(Staticdir)>>=function|Static{services;_}when(notrecurse)&&MethMap.is_emptyservices->Lwt.return(`Not_foundNone)|d->Lwt.return(`Okd))indir:=registerrootservicelookup;!dir(****************************************************************************
* Let's currify!
****************************************************************************)openCurryletregister0rootsf=registerroots(curryZf)letregister1rootsf=registerroots(curry(SZ)f)letregister2rootsf=registerroots(curry(S(SZ))f)letregister3rootsf=registerroots(curry(S(S(SZ)))f)letregister4rootsf=registerroots(curry(S(S(S(SZ))))f)letregister5rootsf=registerroots(curry(S(S(S(S(SZ)))))f)letregister_dynamic_directory1?descrrootsf=register_dynamic_directory?descrroots(curry(SZ)f)letregister_dynamic_directory2?descrrootsf=register_dynamic_directory?descrroots(curry(S(SZ))f)letregister_dynamic_directory3?descrrootsf=register_dynamic_directory?descrroots(curry(S(S(SZ)))f)end