123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332(*****************************************************************************)(* *)(* Open Source License *)(* Copyright 2014 OCamlPro *)(* Copyright (c) 2020 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. *)(* *)(*****************************************************************************)typepath=path_itemlistandpath_item=[`Fieldofstring|`Indexofint|`Star|`Next]exceptionIllegal_pointer_notationofstring*int*stringexceptionUnsupported_path_itemofpath_item*stringexceptionCannot_mergeofpath(*-- path operations -------------------------------------------------------*)letprint_path_as_json_path?(wildcards=true)ppf=function|[]->Format.fprintfppf"/"|nonempty->letrecprintppf=function|[]->()|`Fieldn::rem->Format.fprintfppf"/%s%a"nprintrem|`Indexn::rem->Format.fprintfppf"[%d]%a"nprintrem|`Next::remwhenwildcards->Format.fprintfppf"-%a"printrem|`Star::remwhenwildcards->Format.fprintfppf"*%a"printrem|(`Next|`Star)::_->raise(Unsupported_path_item(`Star,"JSON path w/o wildcards"))inprintppfnonemptyletprint_path_as_json_pointer?(wildcards=true)ppf=function|[]->Format.fprintfppf"/"|nonempty->letrecprintppf=function|[]->()|`Fieldn::rem->Format.fprintfppf"/%s%a"nprintrem|`Indexn::rem->Format.fprintfppf"/%d%a"nprintrem|`Next::remwhenwildcards->Format.fprintfppf"/-%a"printrem|`Next::_->raise(Unsupported_path_item(`Star,"JSON pointer w/o wildcards"))|`Star::_->raise(Unsupported_path_item(`Star,"JSON pointer"))inprintppfnonemptyletjson_pointer_of_path?wildcardspath=Format.asprintf"%a"(print_path_as_json_pointer?wildcards)pathletpath_of_json_pointer?(wildcards=true)str=letbuf=Buffer.create100inletlen=String.lengthstrinletrecslashesacci=ifi>=lenthenList.revaccelseifstr.[i]='/'thenslashesacc(i+1)elseitemaccianditemacci=ifi>=lenthenList.rev(interp()::acc)elsematchstr.[i]with|'/'->slashes(interp()::acc)i|'~'->ifi+1>=lenthenraise(Illegal_pointer_notation(str,i,"Unterminated escape sequence"));(matchstr.[i]with|'0'->Buffer.add_charbuf'~'|'1'->Buffer.add_charbuf'/'|_illegal->raise(Illegal_pointer_notation(str,i+1,"Illegal escape character")));itemacc(i+1)|unescaped->Buffer.add_charbufunescaped;itemacc(i+1)andinterp()=letfield=Buffer.contentsbufinBuffer.clearbuf;iffield="-"thenifwildcardsthen`Nextelseraise(Unsupported_path_item(`Next,"JSON pointer w/o wildcards"))elsetry`Index(int_of_stringfield)with_->`Fieldfieldiniflen=0then[]elseifstr.[0]<>'/'thenraise(Illegal_pointer_notation(str,0,"Missing initial slash"))elseslashes[]1(*-- queries ---------------------------------------------------------------*)moduleMake(Repr:Json_repr.Repr)=structletrecquerypathjson=match(path,Repr.viewjson)with|([],_)->json|(`Fieldn::rempath,`O((n',v)::rem))->ifn=n'thenqueryrempathvelsequerypath(Repr.repr(`Orem))|(`Indexi::rempath,`Acells)->leti=ifi<0thenList.lengthcells-ielseiinqueryrempath(List.nthcellsi)|(`Star::rempath,`O((_,v)::rem))->(tryqueryrempathvwithNot_found->querypath(Repr.repr(`Orem)))|(`Star::rempath,`A(v::rem))->(tryqueryrempathvwithNot_found->querypath(Repr.repr(`Arem)))|(_,_)->raiseNot_foundletquery_allpathjson=letres=ref[]inletrecquerypathjson=match(path,Repr.viewjson)with|([],_)->res:=json::!res|(`Fieldn::rempath,`O((n',v)::rem))->ifn=n'thenqueryrempathvelsequerypath(Repr.repr(`Orem))|(`Indexi::rempath,`Acells)->leti=ifi<0thenList.lengthcells-ielseiinqueryrempath(List.nthcellsi)|(`Star::rempath,`Ofields)->List.iter(fun(_,v)->queryrempathv)fields|(`Star::rempath,`Acells)->List.iter(queryrempath)cells|(_,_)->()inquerypathjson;!res(*-- updates ---------------------------------------------------------------*)letsort_fields=List.sort(fun(l,_)(r,_)->comparelr)letequalslr=letreccanonv=matchRepr.viewvwith|`Ol->Repr.repr(`O(List.map(fun(n,o)->(n,canono))l|>sort_fields))|`Al->Repr.repr(`A(List.mapcanonl))|_->vincanonl=canonrletmergelr=letrecmergepathlr=match(Repr.viewl,Repr.viewr)with|(`Ol,`Or)->Repr.repr(`O(merge_fieldspath[](sort_fields(l@r))))|(`Null,v)|(v,`Null)->Repr.reprv|(`Al,`Ar)->Repr.repr(`A(merge_cellspath0[]lr))|_->ifequalslrthenlelseraise(Cannot_merge(List.revpath))andmerge_cellspathiacclr=match(l,r)with|([],rem)|(rem,[])->List.rev_appendaccrem|(l::ls,r::rs)->letitem=merge(`Indexi::path)lrinmerge_cellspath(succi)(item::acc)lsrsandmerge_fieldspathacc=function|(lf,lv)::((rf,rv)::remasrrem)->iflf=rfthenletitem=merge(`Fieldlf::path)lvrvinmerge_fieldspath((lf,item)::acc)remelsemerge_fieldspath((lf,lv)::acc)rrem|([_]|[])aslast->lastinmerge[]lrletinsert?(merge=merge)pathvalueroot=letrevpathsub=letrecloopacc=function|lwhenl==sub->List.revacc|item::items->loop(item::acc)items|[]->(* absurd *)assertfalseinloop[]pathinletmergepathlr=trymergelrwithCannot_mergesub->raise(Cannot_merge(revpathpath@sub))inletrecnullsaccnlast=ifn<=0thenList.rev(last::acc)elsenulls(Repr.repr`Null::acc)(predn)lastinletrecinsert?rootpath=letroot=matchrootwithNone->None|Somerepr->Some(Repr.viewrepr)inmatch(path,root)with(* create objects *)|(`Fieldn::rempath,None)->Repr.repr(`O[(n,insertrempath)])|((`Index0|`Star|`Next)::rempath,None)->Repr.repr(`A[insertrempath])|(`Indexi::rempath,None)->ifi<0thenraise(Cannot_merge(revpathpath));Repr.repr(`A(nulls[](max0(predi))(insertrempath)))|([],None)->value(* insert in existing *)|([],Somevalue')->mergepathvalue(Repr.reprvalue')|(`Fieldn::rempath,Some(`Ofields))->Repr.repr(`O(insert_fields[]nrempathfields))|(`Indexi::rempath,Some(`Acells))->leti=ifi<0thenList.lengthcells-ielseiinifi<0thenraise(Cannot_merge(revpathpath));Repr.repr(`A(insert_cells[]irempathcells))|(`Next::rempath,Some(`Acells))->Repr.repr(`A(List.rev_append(List.revcells)[insertrempath]))(* multiple insertions *)|(`Star::rempath,Some(`Acells))->Repr.repr(`A(List.map(funroot->insert~rootrempath)cells))|(`Star::rempath,Some(`Ofields))->Repr.repr(`O(List.map(fun(n,root)->(n,insert~rootrempath))fields))|([`Star],Someroot)->mergepathvalue(Repr.reprroot)(* FIXME: make explicit unhandled cases *)|(_,Some_)->raise(Cannot_merge(revpathpath))andinsert_fieldsaccnrempathfields=matchfieldswith|[]->List.rev((n,insertrempath)::acc)|(n',root)::remwhenn=n'->List.rev_append((n,insert~rootrempath)::acc)rem|other::rem->insert_fields(other::acc)nrempathremandinsert_cellsaccnrempathcells=match(cells,n)with|([],n)->nullsaccn(insertrempath)|(root::rem,0)->List.rev_append(insert~rootrempath::acc)rem|(other::rem,n)->insert_cells(other::acc)(n-1)rempathremininsert~rootpathletreplacepathvalueroot=insert~merge:(funvalue_prev->value)pathvaluerootletinsertpathvalueroot=insertpathvaluerootendletpath_operator_name=function|`Field_->"field access"|`Index_->"array access"|`Star->"wildcard"|`Next->"array append"letprint_error?print_unknownppferr=matcherrwith|Illegal_pointer_notation(notation,pos,msg)->Format.fprintfppf"@[<v 2>Illegal pointer notation@,At character %d of %S@,%s@]"posnotationmsg|Unsupported_path_item(item,msg)->Format.fprintfppf"Path operator %s unsupported by %s"(path_operator_nameitem)msg|Cannot_merge[]->Format.fprintfppf"Unmergeable objects"|Cannot_mergepath->Format.fprintfppf"Unmergeable objects, incompatibility at %a"(print_path_as_json_path~wildcards:true)path|exn->(matchprint_unknownwith|Someprint_unknown->print_unknownppfexn|None->Format.fprintfppf"Unhandled error %s"(Printexc.to_stringexn))includeMake(Json_repr.Ezjsonm)