123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166openBase(** Each single_error is a path indicating the location within the datastructure in
question that is being validated, along with an error message. *)typesingle_error={path:stringlist;error:Error.t}typet=single_errorlisttype'acheck='a->tletpass:t=[]letfailsmessageasexp_of_a=[{path=[];error=Error.createmessageasexp_of_a}];;letfailmessage=[{path=[];error=Error.of_stringmessage}]letfailfformat=Printf.ksprintffailformatletfail_ssexp=[{path=[];error=Error.create_ssexp}]letcombinet1t2=t1@t2letof_list=List.concatletnamenamet=matchtwith|[]->[](* when successful, avoid the allocation of a closure for [~f], below *)|_->List.mapt~f:(fun{path;error}->{path=name::path;error});;letname_listnl=namen(of_listl)letfail_fnmessage_=failmessageletpass_bool(_:bool)=passletpass_unit(_:unit)=passletprotectfv=tryfvwith|exn->fail_s(Sexp.message"Exception raised during validation"["",sexp_of_exnexn]);;lettry_withf=protect(fun()->f();pass)();;letpath_stringpath=String.concat~sep:"."pathleterrorst=List.mapt~f:(fun{path;error}->Error.to_string_hum(Error.tagerror~tag:(path_stringpath)));;letresult_failt=Or_error.error"validation errors"(List.mapt~f:(fun{path;error}->path_stringpath,error))[%sexp_of:(string*Error.t)List.t][@@cold];;(** [result] is carefully implemented so that it can be inlined -- calling [result_fail],
which is not inlineable, is key to this. *)letresultt=ifList.is_emptytthenOk()elseresult_failtletmaybe_raiset=Or_error.ok_exn(resultt)letvalid_or_errorcheckx=Or_error.map(result(protectcheckx))~f:(fun()->x)letfield_directcheckfld_recordv=letresult=protectcheckvinname(Field.namefld)result;;letfieldcheckrecordfld=letv=Field.getfldrecordinfield_directcheckfldrecordv;;letfield_foldercheckrecord=();funaccfld->fieldcheckrecordfld::acc;;letfield_direct_foldercheck=Staged.stage(funaccfldrecordv->matchfield_directcheckfldrecordvwith|[]->acc(* Avoid allocating a new list in the success case *)|result->result::acc);;letallchecksv=letrecloopchecksverrs=matchcheckswith|[]->errs|check::checks->(matchprotectcheckvwith|[]->loopchecksverrs|err->loopchecksv(err::errs))inof_list(List.rev(loopchecksv[]));;letof_resultf=protect(funv->matchfvwith|Ok()->pass|Errorerror->failerror);;letof_errorf=protect(funv->matchfvwith|Ok()->pass|Errorerror->[{path=[];error}]);;letbooltestf~if_false=protect(funv->iffvthenpasselsefailif_false)letpair~fst~snd(fst_value,snd_value)=of_list[name"fst"(protectfstfst_value);name"snd"(protectsndsnd_value)];;letlist_indexedchecklist=List.mapilist~f:(funiel->name(Int.to_string(i+1))(protectcheckel))|>of_list;;letlist~name:extract_namechecklist=List.maplist~f:(funel->matchprotectcheckelwith|[]->[]|t->(* extra level of protection in case extract_name throws an exception *)protect(funt->name(extract_nameel)t)t)|>of_list;;letalist~nameflist'=list(fun(_,x)->fx)list'~name:(fun(key,_)->namekey)letfirst_failuret1t2=ifList.is_emptyt1thent2elset1letof_error_opt=function|None->pass|Someerror->failerror;;letbounded~name~lower~upper~comparex=matchMaybe_bound.compare_to_interval_exn~lower~upper~comparexwith|In_range->pass|Below_lower_bound->(matchlowerwith|Unbounded->assertfalse|Inclincl->fail(Printf.sprintf"value %s < bound %s"(namex)(nameincl))|Exclexcl->fail(Printf.sprintf"value %s <= bound %s"(namex)(nameexcl)))|Above_upper_bound->(matchupperwith|Unbounded->assertfalse|Inclincl->fail(Printf.sprintf"value %s > bound %s"(namex)(nameincl))|Exclexcl->fail(Printf.sprintf"value %s >= bound %s"(namex)(nameexcl)));;moduleInfix=structlet(++)t1t2=combinet1t2end