12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217(* Abstract representation of JSON schemas. *)(************************************************************************)(* json-data-encoding *)(* *)(* Copyright 2014 OCamlPro *)(* *)(* This file is distributed under the terms of the GNU Lesser General *)(* Public License as published by the Free Software Foundation; either *)(* version 2.1 of the License, or (at your option) any later version, *)(* with the OCaml static compilation exception. *)(* *)(* It is distributed in the hope that it will be useful, *)(* but WITHOUT ANY WARRANTY; without even the implied warranty of *)(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *)(* GNU General Public License for more details. *)(* *)(************************************************************************)(* TODO: validator *)openJson_query(* The currently handled version *)letversion="http://json-schema.org/draft-04/schema#"(*-- types -----------------------------------------------------------------*)(* The root of a schema with the named definitions,
a precomputed ID-element map and a cache for external documents. *)typeschema={root:element;source:Uri.t(* whose fragment should be empty *);definitions:(path*element)list;ids:(string*element)list;world:schemalist}andelement={title:stringoption;description:stringoption;default:Json_repr.anyoption;enum:Json_repr.anylistoption;kind:element_kind;format:stringoption;id:stringoption}andelement_kind=|Objectofobject_specs|Arrayofelementlist*array_specs|Monomorphic_arrayofelement*array_specs|Combineofcombinator*elementlist|Def_refofpath|Id_refofstring|Ext_refofUri.t|Stringofstring_specs|Integerofnumeric_specs|Numberofnumeric_specs|Boolean|Null|Any|Dummyandcombinator=|Any_of|One_of|All_of|Notandarray_specs={min_items:int;max_items:intoption;unique_items:bool;additional_items:elementoption}andnumeric_specs={multiple_of:floatoption;minimum:(float*[`Inclusive|`Exclusive])option;maximum:(float*[`Inclusive|`Exclusive])option}andobject_specs={properties:(string*element*bool*Json_repr.anyoption)list;pattern_properties:(string*element)list;additional_properties:elementoption;min_properties:int;max_properties:intoption;schema_dependencies:(string*element)list;property_dependencies:(string*stringlist)list}andstring_specs={pattern:stringoption;min_length:int;max_length:intoption}(* box an element kind without any optional field *)letelementkind={title=None;description=None;default=None;kind;format=None;enum=None;id=None}(*-- equality --------------------------------------------------------------*)letoption_mapf=functionNone->None|Somev->Some(fv)letreceq_elementab=a==b||(a.title=b.title&&a.description=b.description&&option_mapJson_repr.from_anya.default=option_mapJson_repr.from_anyb.default&&option_map(List.mapJson_repr.from_any)a.enum=option_map(List.mapJson_repr.from_any)b.enum&&eq_kinda.kindb.kind&&a.format=b.format&&a.id=b.id)andeq_kindab=matcha,bwith|Objectaa,Objectab->eq_object_specsaaab|Array(esa,sa),Array(esb,sb)->List.lengthesa=List.lengthesb&&List.for_all2eq_elementesaesb&&eq_array_specssasb|Monomorphic_array(ea,sa),Monomorphic_array(eb,sb)->eq_elementeaeb&&eq_array_specssasb|Combine(ca,esa),Combine(cb,esb)->ca=cb&&List.lengthesa=List.lengthesb&&List.for_all2eq_elementesaesb|Def_refpa,Def_refpb->pa=pb|Id_refra,Id_refrb->ra=rb|Ext_refra,Ext_refrb->ra=rb|Stringsa,Stringsb->sa=sb|Integerna,Integernb->na=nb|Numberna,Numbernb->na=nb|Boolean,Boolean->true|Null,Null->true|Any,Any->true|Dummy,Dummy->true|_->falseandeq_object_specsab=a.min_properties=b.min_properties&&a.max_properties=b.max_properties&&List.sortcomparea.property_dependencies=List.sortcompareb.property_dependencies&&beginmatcha.additional_properties,b.additional_propertieswith|Somea,Someb->eq_elementab|None,None->true|_,_->falseend&&List.lengtha.pattern_properties=List.lengthb.pattern_properties&&List.for_all2(fun(na,ea)(nb,eb)->na=nb&&eq_elementeaeb)(List.sort(fun(x,_)(y,_)->comparexy)a.pattern_properties)(List.sort(fun(x,_)(y,_)->comparexy)b.pattern_properties)&&List.lengtha.schema_dependencies=List.lengthb.schema_dependencies&&List.for_all2(fun(na,ea)(nb,eb)->na=nb&&eq_elementeaeb)(List.sort(fun(x,_)(y,_)->comparexy)a.schema_dependencies)(List.sort(fun(x,_)(y,_)->comparexy)b.schema_dependencies)&&List.lengtha.properties=List.lengthb.properties&&List.for_all2(fun(na,ea,ra,da)(nb,eb,rb,db)->na=nb&&eq_elementeaeb&&ra=rb&&option_mapJson_repr.from_anyda=option_mapJson_repr.from_anydb)(List.sort(fun(x,_,_,_)(y,_,_,_)->comparexy)a.properties)(List.sort(fun(x,_,_,_)(y,_,_,_)->comparexy)b.properties)andeq_array_specsab=a.min_items=b.min_items&&a.max_items=b.max_items&&a.unique_items=b.unique_items&&matcha.additional_items,b.additional_itemswith|Somea,Someb->eq_elementab|None,None->true|_,_->false(*-- human readable output -------------------------------------------------*)letpp_stringppfs=Json_repr.(pp(moduleEzjsonm))ppf(`Strings)letpp_numppfm=ifabs_floatm<1000.thenFormat.fprintfppf"%g"melseletpos,m=ifm<0.then(false,~-.m)else(true,m)inifList.fold_left(funaccd->ifaccthenaccelseletv=log(m+.d)/.log2.inifabs_float(ceilv-.v)<0.00001thenbeginFormat.fprintfppf"%s2^%g"(ifposthen""else"-")v;if(pos&&d<0.)||(notpos&&d>0.)thenFormat.fprintfppf"+%g"(abs_floatd);if(pos&&d>0.)||(notpos&&d<0.)thenFormat.fprintfppf"-%g"(abs_floatd);trueendelsefalse)false[-2.;-1.;0.;1.;2.]then()elseFormat.fprintfppf"%f"mletpp_numeric_specsppf{multiple_of;minimum;maximum}=Format.fprintfppf"%a%a%a"(funppf->functionNone->()|Somev->Format.fprintfppf"multiple of %g"v)multiple_of(funppf->function|(None,_,_)|(_,None,None)->()|_->Format.fprintfppf", ")(multiple_of,minimum,maximum)(funppf->function|None,None->()|minimum,maximum->Format.fprintfppf"∈ %a, %a"(funppf->function|None->Format.fprintfppf"]∞"|Some(m,`Exclusive)->Format.fprintfppf"]%a"pp_numm|Some(m,`Inclusive)->Format.fprintfppf"[%a"pp_numm)minimum(funppf->function|None->Format.fprintfppf"∞["|Some(m,`Exclusive)->Format.fprintfppf"%a["pp_numm|Some(m,`Inclusive)->Format.fprintfppf"%a]"pp_numm)maximum)(minimum,maximum)letpp_pathppf=function|[`Field"definitions";`Fieldname]->Format.fprintfppf"%s"name|path->Json_query.(print_path_as_json_path~wildcards:true)ppfpathletpp_descelement=matchelementwith|{title=None;description=None}->None|{title=Sometext;description=None}|{title=None;description=Sometext}->Somebeginfunppf()->Format.fprintfppf"/* @[<hov 0>%a@] */"Format.pp_print_texttextend|{title=Sometitle;description=Somedescription}->Somebeginfunppf()->Format.fprintfppf"/* @[<v 0>@[<hov 0>%a@]@,@[<hov 0>%a@]@] */"Format.pp_print_texttitleFormat.pp_print_textdescriptionendletrecpp_elementppfelement=matchelement.idwith|Someid->Format.fprintfppf"#%s"id|None->matchelement.formatwith|Someformat->Format.fprintfppf"%s"format|None->matchelement.enumwith|Somecases->letpp_sepppf()=Format.fprintfppf"@ | "inFormat.fprintfppf"@[<hv 0>%a@]"(Format.pp_print_list~pp_sep(Json_repr.pp_any~compact:false()))cases|None->matchpp_descelementwith|Somepp_desc->letstripped={elementwithtitle=None;description=None}inbeginmatchelement.kindwith|Combine_->Format.fprintfppf"%a@,%a"pp_desc()pp_elementstripped|Objectspecs->Format.fprintfppf"@[<v 2>{ %a@,%a }@]"pp_desc()pp_object_contentsspecs|_->Format.fprintfppf"%a@ %a"pp_elementstrippedpp_desc()end|None->beginmatchelement.kindwith|String{pattern=None;min_length=0;max_length=None}->Format.fprintfppf"string"|String{pattern=Somepat;min_length=0;max_length=None}->Format.fprintfppf"/%s/"pat|String{pattern;min_length;max_length}->Format.fprintfppf"%a (%alength%a)"(funppf->function|None->Format.fprintfppf"string"|Somepat->Format.fprintfppf"/%s/"pat)pattern(funppfn->ifn>0thenFormat.fprintfppf"%d <= "n)min_length(funppf->functionNone->()|Somem->Format.fprintfppf"<= %d"m)max_length|Integer{multiple_of=None;minimum=None;maximum=None}->Format.fprintfppf"integer"|Integerspecs->Format.fprintfppf"integer %a"pp_numeric_specsspecs|Number{multiple_of=None;minimum=None;maximum=None}->Format.fprintfppf"number"|Numberspecs->Format.fprintfppf"number %a"pp_numeric_specsspecs|Id_refid->Format.fprintfppf"#%s"id|Def_refpath->Format.fprintfppf"$%a"pp_pathpath|Ext_refuri->Format.fprintfppf"$%a"Uri.pp_humuri|Boolean->Format.fprintfppf"boolean"|Null->Format.fprintfppf"null"|Any->Format.fprintfppf"any"|Dummy->assertfalse|Combine(Not,[elt])->Format.fprintfppf"! %a"pp_elementelt|Combine(c,elts)->letpp_sepppf()=matchcwith|Any_of->Format.fprintfppf"@ | "|One_of->Format.fprintfppf"@ || "|All_of->Format.fprintfppf"@ && "|_->assertfalseinFormat.fprintfppf"@[<hv 0>%a@]"(Format.pp_print_list~pp_seppp_element)elts|Object{properties=[];pattern_properties=[];additional_properties=None;min_properties=0;max_properties=Some0;schema_dependencies=[];property_dependencies=[]}->Format.fprintfppf"{}"|Objectspecs->Format.fprintfppf"@[<v 2>{ %a }@]"pp_object_contentsspecs|Array(_,{max_items=Some0})|Monomorphic_array(_,{max_items=Some0})->Format.fprintfppf"[]"|Array(elements,{additional_items})->letpp_sep=letfirst=reftrueinfunppf()->if!firstthenfirst:=falseelseFormat.fprintfppf",@ "inFormat.fprintfppf"@[<hv 2>[ ";List.iter(funelt->Format.fprintfppf"%a%a"pp_sep()pp_elementelt)elements;beginmatchadditional_itemswith|None->()|Some{kind=Any}->Format.fprintfppf"%a,@ ..."pp_sep()|Someelt->Format.fprintfppf"%a,@ %a ..."pp_sep()pp_elementeltend;Format.fprintfppf" ]@]"|Monomorphic_array(elt,{additional_items=None})->Format.fprintfppf"[ %a ... ]"pp_elementelt|Monomorphic_array(elt,{additional_items=Some{kind=Any}})->Format.fprintfppf"@[<hv 2>[ %a ...,@ ... ]@]"pp_elementelt|Monomorphic_array(elt,{additional_items=Someadd_elt})->(* TODO: find a good way to print length *)Format.fprintfppf"@[<hv 2>[ %a ...,@ %a ... ]@]"pp_elementeltpp_elementadd_eltendandpp_object_contentsppf{properties;pattern_properties;additional_properties}=(* TODO: find a good way to print length / dependencies *)letpp_sep=letfirst=reftrueinfunppf()->if!firstthenfirst:=falseelseFormat.fprintfppf",@ "inList.iter(fun(name,elt,req,_)->Format.fprintfppf"%a@[<hv 2>%a%s:@ %a@]"pp_sep()pp_stringname(ifreqthen""else"?")pp_elementelt)properties;List.iter(fun(name,elt)->Format.fprintfppf"%a@[<hv 2>/%s/:@ %a@]"pp_sep()namepp_elementelt)pattern_properties;beginmatchadditional_propertieswith|None->()|Some{kind=Any}->Format.fprintfppf"%a..."pp_sep()|Someelt->Format.fprintfppf"%a@[<hv 2>*:@ %a@]"pp_sep()pp_elementeltendletppppfschema=Format.fprintfppf"@[<v 0>";pp_elementppfschema.root;List.iter(fun(path,elt)->matchpp_desceltwith|None->Format.fprintfppf"@,@[<hv 2>$%a:@ %a@]"pp_pathpathpp_elementelt|Somepp_desc->letstripped={eltwithtitle=None;description=None}inFormat.fprintfppf"@,@[<v 2>$%a:@,%a@,%a@]"pp_pathpathpp_desc()pp_elementstripped)schema.definitions;List.iter(fun(id,elt)->matchpp_desceltwith|None->Format.fprintfppf"@,@[<hv 2>#%s:@ %a@]"idpp_element{eltwithid=None}|Somepp_desc->letstripped={eltwithtitle=None;description=None;id=None}inFormat.fprintfppf"@,@[<v 2>#%s:@,%a@,%a@]"idpp_desc()pp_elementstripped)schema.ids;Format.fprintfppf"@]"(*-- errors ----------------------------------------------------------------*)exceptionCannot_parseofpath*exnexceptionDangling_referenceofUri.texceptionBad_referenceofstringexceptionUnexpectedofstring*stringexceptionDuplicate_definitionofpath*element*elementletrecprint_error?print_unknownppf=function|Cannot_parse(path,exn)->Format.fprintfppf"@[<v 2>Schema parse error:@,At %a@,%a@]"(Json_query.print_path_as_json_path~wildcards:true)path(print_error?print_unknown)exn|Dangling_referenceuri->Format.fprintfppf"Dangling reference %s"(Uri.to_stringuri)|Bad_referencestr->Format.fprintfppf"Illegal reference notation %s"str|Unexpected(unex,ex)->Format.fprintfppf"Unexpected %s instead of %s"unexex|Duplicate_definition(name,elt,defelt)->Format.fprintfppf"@[<v 2>Duplicate definition %a@,\
To be inserted:@,\
\ @[<v 0>%a@]@,\
Already present:@,\
\ @[<v 0>%a@]@]"(Json_query.print_path_as_json_pointer~wildcards:false)namepp_elementeltpp_elementdefelt|exn->Json_query.print_error?print_unknownppfexn(*-- internal definition table handling ------------------------------------*)letfind_definitionnamedefs=List.assocnamedefsletdefinition_existsnamedefs=List.mem_assocnamedefsletinsert_definitionnameeltdefs=letrecinsert=function|[]->[(name,elt)]|(defname,_)asdef::remwhendefname<>name->def::insertrem|(_,{kind=Dummy})::rem->(name,elt)::rem|(_,defelt)::rem->ifnot(eq_elementeltdefelt)thenraise(Duplicate_definition(name,elt,defelt));(name,elt)::remininsertdefsmoduleMake(Repr:Json_repr.Repr)=structmoduleQuery=Json_query.Make(Repr)openQuery(*-- printer ---------------------------------------------------------------*)letto_jsonschema=(* functional JSON building combinators *)letobjl=Repr.repr(`Ol)inletset_alwaysfv=[f,Repr.reprv]inletset_if_somefvcb=matchvwithNone->[]|Somev->[f,Repr.repr(cbv)]inletset_if_consfvcb=matchvwith[]->[]|v->[f,Repr.repr(cbv)]inletset_if_neqfvv'cb=ifv<>v'then[f,Repr.repr(cbv)]else[]in(* recursive encoder *)letrecformat_element{title;description;default;enum;kind;format}=set_if_some"title"title(funs->`Strings)@set_if_some"description"description(funs->`Strings)@beginmatchkindwith|Objectspecs->letrequired=List.fold_left(funr(n,_,p,_)->ifpthenRepr.repr(`Stringn)::relser)[]specs.propertiesinletproperties=List.map(fun(n,elt,_,_)->n,obj(format_elementelt))specs.propertiesinset_always"type"(`String"object")@set_always"properties"(`Oproperties)@set_if_cons"required"required(funl->`Al)@set_if_cons"patternProperties"specs.pattern_properties(funfs->`O(List.map(fun(n,elt)->n,obj(format_elementelt))fs))@set_if_neq"additionalProperties"specs.additional_properties(Some(elementAny))(function|None->`Boolfalse|Someelt->`O(format_elementelt))@set_if_neq"minProperties"specs.min_properties0(funi->`Float(floati))@set_if_some"maxProperties"specs.max_properties(funi->`Float(floati))@set_if_cons"schemaDependencies"specs.schema_dependencies(funfs->`O(List.map(fun(n,elt)->n,obj(format_elementelt))fs))@set_if_cons"propertyDependencies"specs.property_dependencies(funfs->letproperty_dependencies=letstringsls=List.map(funs->Repr.repr(`Strings))lsinList.map(fun(n,ls)->n,Repr.repr(`A(stringsls)))fsin`Oproperty_dependencies)|Array(elts,specs)->set_always"type"(`String"array")@set_always"items"(`A(List.map(funelt->obj(format_elementelt))elts))@set_if_neq"minItems"specs.min_items0(funi->`Float(floati))@set_if_some"maxItems"specs.max_items(funi->`Float(floati))@set_if_neq"uniqueItems"specs.unique_itemsfalse(funb->`Boolb)@set_if_neq"additionalItems"specs.additional_items(Some(elementAny))(function|None->`Boolfalse|Someelt->`O(format_elementelt))|Monomorphic_array(elt,{min_items;max_items;unique_items})->set_always"type"(`String"array")@set_always"items"(`O(format_elementelt))@set_if_neq"minItems"min_items0(funi->`Float(floati))@set_if_some"maxItems"max_items(funi->`Float(floati))@set_if_neq"uniqueItems"unique_itemsfalse(funb->`Boolb)|Combine(c,elts)->letcombinator=function|Any_of->"anyOf"|One_of->"oneOf"|All_of->"allOf"|Not->"not"inset_always(combinatorc)(`A(List.map(funelt->obj(format_elementelt))elts))|Def_refpath->set_always"$ref"(`String("#"^(json_pointer_of_pathpath)))|Id_refname->set_always"$ref"(`String("#"^name))|Ext_refuri->set_always"$ref"(`String(Uri.to_stringuri))|Integerspecs->set_always"type"(`String"integer")@set_if_some"multipleOf"specs.multiple_of(funv->`Floatv)@(matchspecs.minimumwith|None->[]|Some(v,`Inclusive)->["minimum",Repr.repr(`Floatv)]|Some(v,`Exclusive)->["minimum",Repr.repr(`Floatv);"exclusiveMinimum",Repr.repr(`Booltrue)])@(matchspecs.maximumwith|None->[]|Some(v,`Inclusive)->["maximum",Repr.repr(`Floatv)]|Some(v,`Exclusive)->["maximum",Repr.repr(`Floatv);"exclusiveMaximum",Repr.repr(`Booltrue)])|Numberspecs->set_always"type"(`String"number")@set_if_some"multipleOf"specs.multiple_of(funv->`Floatv)@(matchspecs.minimumwith|None->[]|Some(v,`Inclusive)->["minimum",Repr.repr(`Floatv)]|Some(v,`Exclusive)->["minimum",Repr.repr(`Floatv);"exclusiveMinimum",Repr.repr(`Booltrue)])@(matchspecs.maximumwith|None->[]|Some(v,`Inclusive)->["maximum",Repr.repr(`Floatv)]|Some(v,`Exclusive)->["maximum",Repr.repr(`Floatv);"exclusiveMaximum",Repr.repr(`Booltrue)])|String{pattern;min_length;max_length}->set_always"type"(`String"string")@set_if_neq"minLength"min_length0(funi->`Float(floati))@set_if_some"maxLength"max_length(funi->`Float(floati))@set_if_some"pattern"pattern(funs->`Strings)|Boolean->set_always"type"(`String"boolean")|Null->set_always"type"(`String"null")|Dummy->invalid_arg"Json_schema.to_json: remaining dummy element"|Any->[]end@set_if_some"default"default(funj->Repr.view(Json_repr.any_to_repr(moduleRepr)j))@set_if_some"enum"enum(funjs->`A(List.map(Json_repr.any_to_repr(moduleRepr))js))@set_if_some"format"format(funs->`Strings)inList.fold_left(funacc(n,elt)->insertn(obj(format_elementelt))acc)(obj(set_always"$schema"(`Stringversion)@format_elementschema.root))schema.definitionsletunexpectedkindexpected=letkind=matchkindwith|`O[]->"empty object"|`A[]->"empty array"|`O_->"object"|`A_->"array"|`Null->"null"|`String""->"empty string"|`String_->"string"|`Float_->"number"|`Bool_->"boolean"inCannot_parse([],Unexpected(kind,expected))(*-- parser ----------------------------------------------------------------*)letat_pathp=functionCannot_parse(l,err)->Cannot_parse(p@l,err)|exn->exnletat_fieldn=at_path[`Fieldn]letat_indexi=at_path[`Indexi]letof_jsonjson=(* parser combinators *)letopt_fieldobjn=matchRepr.viewobjwith|`Ols->(trySome(List.assocnls)withNot_found->None)|_->Noneinletopt_field_viewobjn=matchRepr.viewobjwith|`Ols->(trySome(Repr.view(List.assocnls))withNot_found->None)|_->Noneinletopt_string_fieldobjn=matchopt_field_viewobjnwith|Some(`Strings)->Somes|Somek->raise(at_fieldn@@unexpectedk"string")|None->Noneinletopt_bool_fielddefobjn=matchopt_field_viewobjnwith|Some(`Boolb)->b|Somek->raise(at_fieldn@@unexpectedk"bool")|None->definletopt_int_fieldobjn=matchopt_field_viewobjnwith|Some(`Floatf)when(fst(modff)=0.&&f<=2.**53.&&f>=-2.**53.)->Somef|Somek->raise(at_fieldn@@unexpectedk"integer")|None->Noneinletopt_length_fieldobjn=matchopt_field_viewobjnwith|Some(`Floatf)when(fst(modff)=0.&&f<=2.**30.&&f>=0.)->Some(int_of_floatf)|Somek->raise(at_fieldn@@unexpectedk"length")|None->Noneinletopt_float_fieldobjn=matchopt_field_viewobjnwith|Some(`Floatf)->Somef|Somek->raise(at_fieldn@@unexpectedk"number")|None->Noneinletopt_array_fieldobjn=matchopt_field_viewobjnwith|Some(`As)->Somes|Somek->raise(at_fieldn@@unexpectedk"array")|None->Noneinletopt_uri_fieldobjn=matchopt_string_fieldobjnwith|None->None|Someuri->matchUri.canonicalize(Uri.of_stringuri)with|exception_->raise(Cannot_parse([],Bad_reference(uri^" is not a valid URI")))|uri->Someuriin(* local resolution of definitions *)letschema_source=matchopt_uri_fieldjson"id"with|Someuri->Uri.with_fragmenturiNone|None->Uri.emptyinletcollected_definitions=ref[]inletcollected_id_defs=ref[]inletcollected_id_refs=ref[]inletreccollect_definition:Uri.t->element_kind=funuri->matchUri.hosturi,Uri.fragmenturiwith|Some_(* Actually means: any of host, user or port is defined. *),_->Ext_refuri|None,None->raise(Cannot_parse([],Bad_reference(Uri.to_stringuri^" has no fragment")))|None,Somefragmentwhennot(String.containsfragment'/')->collected_id_refs:=fragment::!collected_id_refs;Id_reffragment|None,Somefragment->letpath=trypath_of_json_pointer~wildcards:falsefragmentwitherr->raise(Cannot_parse([],err))intryletraw=querypathjsoninifnot(definition_existspath!collected_definitions)thenbegin(* dummy insertion so we don't recurse and we support cycles *)collected_definitions:=insert_definitionpath(elementDummy)!collected_definitions;letelt=tryparse_elementschema_sourcerawwitherr->raise(at_pathpatherr)in(* actual insertion *)collected_definitions:=insert_definitionpathelt!collected_definitionsend;Def_refpathwithNot_found->raise(Cannot_parse([],Dangling_referenceuri))(* recursive parser *)andparse_element:Uri.t->Repr.value->element=funsourcejson->letid=opt_uri_fieldjson"id"inletid,source=matchidwith|None->None,source|Someuri->leturi=Uri.canonicalize(Uri.resolve"http"sourceuri)inUri.fragmenturi,Uri.with_fragmenturiNonein(* We don't support inlined schemas, so we just drop elements with
external sources and replace them with external references. *)ifsource<>schema_sourcethenelement(Ext_ref(Uri.with_fragmentsourceid))elseletid=matchidwith|None->None|SomeidwhenString.containsid'/'->raise(at_field"id"@@Cannot_parse([],Bad_reference(id^" is not a valid ID")))|Someid->Someidin(* We parse the various element syntaxes and combine them afterwards. *)(* 1. An element with a known type field and associated fields. *)letas_kind=matchopt_field_viewjson"type"with|Some(`Stringname)->Some(element(parse_element_kindsourcejsonname))|Some(`A[]ask)->raise(at_field"type"@@unexpectedk"type, type array or operator")|Some(`Al)->letrecitemsiacc=function|[]->letkind=Combine(Any_of,List.revacc)inSome(elementkind)|`Stringname::tl->letkind=parse_element_kindsourcejsonnameinletcase=elementkindinitems(succi)(case::acc)tl|k::_->raise(at_field"type"@@at_indexi@@unexpectedk"type")initems0[](List.mapRepr.viewl)|Somek->raise(at_field"type"@@unexpectedk"type, type array or operator")|None->Nonein(* 2. A reference *)letas_ref=matchopt_uri_fieldjson"$ref"with|Someuri->letpath=collect_definitionuriinSome(elementpath)|None->Nonein(* 3. Combined schemas *)letas_narynamecombinatorothers=letbuild=function|[]->None(* not found and no auxiliary case *)|[case]->Somecase(* one case -> simplify *)|cases->(* several cases build the combination node with empty options *)letkind=Combine(combinator,cases)inSome(elementkind)inmatchopt_field_viewjsonnamewith|Some(`A(_::_ascases))(* list of schemas *)->letrecitemsiacc=function|elt::tl->letelt=tryparse_elementsourceeltwitherr->raise(at_fieldname@@at_indexi@@err)initems(succi)(elt::acc)tl|[]->build(others@List.revacc)initems0[]cases|None->buildothers|Somek->raise(at_fieldname@@unexpectedk"a list of elements")in(* 4. Negated schema *)letas_not=matchopt_field_viewjson"not"with|None->None|Someelt->letelt=tryparse_elementsource(Repr.reprelt)witherr->raise(at_field"not"err)inletkind=Combine(Not,[elt])inSome(elementkind)in(* parse optional fields *)lettitle=opt_string_fieldjson"title"inletdescription=opt_string_fieldjson"description"inletdefault=matchopt_fieldjson"default"with|Somev->Some(Json_repr.repr_to_any(moduleRepr)v)|None->Noneinletenum=matchopt_array_fieldjson"enum"with|Somev->Some(List.map(Json_repr.repr_to_any(moduleRepr))v)|None->Noneinletformat=opt_string_fieldjson"format"in(* TODO: check format ? *)(* combine all specifications under a big conjunction *)letas_one_of=as_nary"oneOf"One_of[]inletas_any_of=as_nary"anyOf"Any_of[]inletall=[as_kind;as_ref;as_not;as_one_of;as_any_of]inletcases=List.flatten(List.map(functionNone->[]|Somee->[e])all)inletkind=matchas_nary"allOf"All_ofcaseswith|None->Any(* no type, ref or logical combination found *)|Some{kind}->kindin(* add optional fields *){title;description;default;format;kind;enum;id}andparse_element_kindsourcejsonname=letinteger_specsjson=letmultiple_of=opt_int_fieldjson"multipleOf"inletminimum=ifopt_bool_fieldfalsejson"exclusiveMinimum"thenmatchopt_int_fieldjson"minimum"with|None->leterr="minimum field required when exclusiveMinimum is true"inraise(Failureerr)|Somev->Some(v,`Inclusive)elsematchopt_int_fieldjson"minimum"with|None->None|Somev->Some(v,`Exclusive)inletmaximum=ifopt_bool_fieldfalsejson"exclusiveMaximum"thenmatchopt_int_fieldjson"maximum"with|None->leterr="maximum field required when exclusiveMaximum is true"inraise(Failureerr)|Somev->Some(v,`Inclusive)elsematchopt_int_fieldjson"maximum"with|None->None|Somev->Some(v,`Exclusive)in{multiple_of;minimum;maximum}inletnumeric_specsjson=letmultiple_of=opt_float_fieldjson"multipleOf"inletminimum=ifopt_bool_fieldfalsejson"exclusiveMinimum"thenmatchopt_float_fieldjson"minimum"with|None->leterr="minimum field required when exclusiveMinimum is true"inraise(Failureerr)|Somev->Some(v,`Inclusive)elsematchopt_float_fieldjson"minimum"with|None->None|Somev->Some(v,`Exclusive)inletmaximum=ifopt_bool_fieldfalsejson"exclusiveMaximum"thenmatchopt_float_fieldjson"maximum"with|None->leterr="maximum field required when exclusiveMaximum is true"inraise(Failureerr)|Somev->Some(v,`Inclusive)elsematchopt_float_fieldjson"maximum"with|None->None|Somev->Some(v,`Exclusive)in{multiple_of;minimum;maximum}inmatchnamewith|"integer"->Integer(integer_specsjson)|"number"->Number(numeric_specsjson)|"boolean"->Boolean|"null"->Null|"string"->letspecs=letpattern=opt_string_fieldjson"pattern"inletmin_length=opt_length_fieldjson"minLength"inletmax_length=opt_length_fieldjson"maxLength"inletmin_length=matchmin_lengthwithNone->0|Somel->lin{pattern;min_length;max_length}inStringspecs|"array"->letspecs=letunique_items=opt_bool_fieldfalsejson"uniqueItems"inletmin_items=opt_length_fieldjson"minItems"inletmax_items=opt_length_fieldjson"maxItems"inletmin_items=matchmin_itemswithNone->0|Somel->linmatchopt_field_viewjson"additionalItems"with|Some(`Booltrue)->{min_items;max_items;unique_items;additional_items=Some(elementAny)}|None|Some(`Boolfalse)->{min_items;max_items;unique_items;additional_items=None}|Someelt->letelt=tryparse_elementsource(Repr.reprelt)witherr->raise(at_field"additionalItems"err)in{min_items;max_items;unique_items;additional_items=Someelt}inbeginmatchopt_field_viewjson"items"with|Some(`Aelts)->letrecelementsiacc=function|[]->Array(List.revacc,specs)|elt::tl->letelt=tryparse_elementsourceeltwitherr->raise(at_field"items"@@at_indexierr)inelements(succi)(elt::acc)tlinelements0[]elts|Someelt->letelt=tryparse_elementsource(Repr.reprelt)witherr->raise(at_field"items"err)inMonomorphic_array(elt,specs)|None->Monomorphic_array(elementAny,specs)end|"object"->letrequired=matchopt_array_fieldjson"required"with|None->[]|Somel->letrecitemsiacc=function|`Strings::tl->items(succi)(s::acc)tl|[]->List.revacc|k::_->raise(at_field"required"@@at_indexi@@unexpectedk"string")initems0[](List.mapRepr.viewl)inletproperties=matchopt_field_viewjson"properties"with|Some(`Oprops)->letrecitemsacc=function|[]->List.revacc|(n,elt)::tl->letelt=tryparse_elementsourceeltwitherr->raise(at_field"properties"@@at_fieldn@@err)inletreq=List.memnrequiredinitems((n,elt,req,None)::acc)tl(* XXX: fixme *)initems[]props|None->[]|Somek->raise(at_field"properties"@@unexpectedk"object")inletadditional_properties=matchopt_field_viewjson"additionalProperties"with|Some(`Boolfalse)->None|None|Some(`Booltrue)->Some(elementAny)|Someelt->letelt=tryparse_elementsource(Repr.reprelt)witherr->raise(at_field"additionalProperties"err)inSomeeltinletproperty_dependencies=matchopt_field_viewjson"propertyDependencies"with|None->[]|Some(`Ol)->letrecsetssacc=function|(n,`Al)::tl->letrecstringsjacc=function|[]->sets((n,List.revacc)::sacc)tl|`Strings::tl->strings(succj)(s::acc)tl|k::_->raise(at_field"propertyDependencies"@@at_fieldn@@at_indexj@@unexpectedk"string")instrings0[](List.mapRepr.viewl)|(n,k)::_->raise(at_field"propertyDependencies"@@at_fieldn@@unexpectedk"string array")|[]->List.revsaccinsets[](List.map(fun(n,v)->(n,Repr.viewv))l)|Somek->raise(at_field"propertyDependencies"@@unexpectedk"object")inletparse_element_assocfield=matchopt_field_viewjsonfieldwith|None->[]|Some(`Oprops)->letrecitemsacc=function|[]->List.revacc|(n,elt)::tl->letelt=tryparse_elementsourceeltwitherr->raise(at_fieldfield@@at_fieldnerr)initems((n,elt)::acc)tlinitems[]props|Somek->raise(at_fieldfield@@unexpectedk"object")inletpattern_properties=parse_element_assoc"patternProperties"inletschema_dependencies=parse_element_assoc"schemaDependencies"inletmin_properties=matchopt_length_fieldjson"minProperties"with|None->0|Somel->linletmax_properties=opt_length_fieldjson"maxProperties"inObject{properties;pattern_properties;additional_properties;min_properties;max_properties;schema_dependencies;property_dependencies}|n->raise(Cannot_parse([],Unexpected(n,"a known type")))in(* parse recursively from the root *)letroot=parse_elementUri.emptyjsonin(* force the addition of everything inside /definitions *)(matchRepr.view(query[`Field"definitions"]json)with|`Oall->letall=List.map(fun(n,_)->Uri.of_string("#/definitions/"^n))allinList.iter(funuri->collect_definitionuri|>ignore)all|_->()|exceptionNot_found->());(* check the domain of IDs *)List.iter(funid->ifnot(List.mem_associd!collected_id_defs)thenraise(Cannot_parse([],Dangling_reference(Uri.(with_fragmentempty(Someid))))))!collected_id_refs;letids=!collected_id_defsinletsource=schema_sourceinletworld=[]inletdefinitions=!collected_definitionsin{root;definitions;source;ids;world}(*-- creation and update ---------------------------------------------------*)(* Checks that all local refs and ids are defined *)letcheck_definitionsrootdefinitions=letcollected_id_defs=ref[]inletcollected_id_refs=ref[]inletreccheck({kind;id}aselt)=beginmatchidwith|None->()|Someid->collected_id_defs:=(id,elt)::!collected_id_defsend;beginmatchkindwith|Object{properties;pattern_properties;additional_properties;schema_dependencies}->List.iter(fun(_,e,_,_)->checke)properties;List.iter(fun(_,e)->checke)pattern_properties;List.iter(fun(_,e)->checke)schema_dependencies;(matchadditional_propertieswithSomee->checke|None->())|Array(es,{additional_items})->List.itercheckes;(matchadditional_itemswithSomee->checke|None->())|Monomorphic_array(e,{additional_items})->checke;(matchadditional_itemswithSomee->checke|None->())|Combine(_,es)->List.itercheckes|Def_refpath->ifnot(definition_existspathdefinitions)thenletpath=json_pointer_of_pathpathinraise(Dangling_reference(Uri.(with_fragmentempty)(Somepath)))|Id_refid->collected_id_refs:=id::!collected_id_refs;|Ext_ref_|String_|Integer_|Number_|Boolean|Null|Any|Dummy->()endin(* check the root and definitions *)checkroot;List.iter(fun(_,root)->checkroot)definitions;(* check the domain of IDs *)List.iter(funid->ifnot(List.mem_associd!collected_id_defs)thenraise(Dangling_reference(Uri.(with_fragmentempty(Someid)))))!collected_id_refs;!collected_id_defsletcreateroot=letids=check_definitionsroot[]in{root;definitions=[];world=[];ids;source=Uri.empty}letroot{root}=rootletupdaterootsch=letids=check_definitionsrootsch.definitionsin{schwithroot;ids}letany=create(elementAny)letself={root=element(Ext_ref(Uri.of_stringversion));definitions=[];ids=[];world=[];source=Uri.empty}(* remove unused definitions from the schema *)letsimplifyschema=letres=ref[](* collected definitions *)inletreccollect{kind}=matchkindwith|Object{properties;pattern_properties;additional_properties;schema_dependencies}->List.iter(fun(_,e,_,_)->collecte)properties;List.iter(fun(_,e)->collecte)pattern_properties;List.iter(fun(_,e)->collecte)schema_dependencies;(matchadditional_propertieswithSomee->collecte|None->())|Array(es,{additional_items})->List.itercollectes;(matchadditional_itemswithSomee->collecte|None->())|Monomorphic_array(e,{additional_items})->collecte;(matchadditional_itemswithSomee->collecte|None->())|Combine(_,es)->List.itercollectes|Def_refpath->letdef=find_definitionpathschema.definitionsinres:=insert_definitionpathdef!res|Ext_ref_|Id_ref_|String_|Integer_|Number_|Boolean|Null|Any|Dummy->()incollectschema.root;{schemawithdefinitions=!res}letdefinition_path_of_name?(definitions_path="/definitions/")name=path_of_json_pointer~wildcards:false@@matchString.getname0with|exception_->raise(Bad_referencename)|'/'->name|_->definitions_path^nameletfind_definition?definitions_pathnameschema=letpath=definition_path_of_name?definitions_pathnameinfind_definitionpathschema.definitionsletdefinition_ref?definitions_pathname=letpath=definition_path_of_name?definitions_pathnameinelement(Def_refpath)letdefinition_exists?definitions_pathnameschema=letpath=definition_path_of_name?definitions_pathnameindefinition_existspathschema.definitionsletadd_definition?definitions_pathnameeltschema=letpath=definition_path_of_name?definitions_pathnamein(* check inside def *)letdefinitions=insert_definitionpatheltschema.definitionsin{schemawithdefinitions},element(Def_refpath)letmerge_definitions(sa,sb)=letrecsorted_merge=function|((na,da)asa)::((nb,db)asb)::tl->ifna=nbthenifda.kind=Dummy||db.kind=Dummy||eq_elementdadbthen(na,da)::sorted_mergetlelseraise(Duplicate_definition(na,da,db))elsea::sorted_merge(b::tl)|[]|[_]asrem->reminletdefinitions=sorted_merge(List.sortcompare(sa.definitions@sb.definitions))in{sawithdefinitions},{sbwithdefinitions}letcombineopschemas=letreccombinesacceacc=function|[]->update(element(Combine(op,eacc)))sacc|s::ss->letsacc,s=merge_definitions(sacc,s)incombinesacc(s.root::eacc)ssincombineany[]schemasletis_nullable{ids;definitions;root}=letrecnullable{kind}=matchkindwith|Null|Any->true|Object_|Array_|Monomorphic_array_|Ext_ref_|String_|Integer_|Number_|Boolean->false|Combine(Not,[elt])->not(nullableelt)|Combine(All_of,elts)->List.for_allnullableelts|Combine((Any_of|One_of),elts)->List.existsnullableelts|Def_refpath->nullable(List.assocpathdefinitions)|Id_refid->nullable(List.associdids)|Combine(Not,_)|Dummy->assertfalseinnullableroot(*-- default specs ---------------------------------------------------------*)letarray_specs={min_items=0;max_items=None;unique_items=false;additional_items=None}letobject_specs={properties=[];pattern_properties=[];additional_properties=Some(elementAny);min_properties=0;max_properties=None;schema_dependencies=[];property_dependencies=[]}letstring_specs={pattern=None;min_length=0;max_length=None}letnumeric_specs={multiple_of=None;minimum=None;maximum=None}endincludeMake(Json_repr.Ezjsonm)