123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184open!ImportmoduleInt=Int0moduleString=String0(** 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=tryfvwithexn->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))));;let[@inlinenever]result_failt=Or_error.error"validation errors"(List.mapt~f:(fun{path;error}->(path_stringpath,error)))(sexp_of_list(sexp_of_pairsexp_of_stringError.sexp_of_t));;(** [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_failt;;letmaybe_raiset=Or_error.ok_exn(resultt)letvalid_or_errorxcheck=Or_error.map(result(protectcheckx))~f:(fun()->x);;letfieldrecordfldf=letv=Field.getfldrecordinletresult=protectfvinname(Field.namefld)result;;letfield_folderrecordcheck=();funaccfld->fieldrecordfldcheck::accletfield_direct_foldercheck=Staged.stage(funaccfld_recordv->matchprotectcheckvwith|[]->acc|result->name(Field.namefld)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->beginmatchlowerwith|Unbounded->assertfalse|Inclincl->fail(Printf.sprintf"value %s < bound %s"(namex)(nameincl))|Exclexcl->fail(Printf.sprintf"value %s <= bound %s"(namex)(nameexcl))end|Above_upper_bound->beginmatchupperwith|Unbounded->assertfalse|Inclincl->fail(Printf.sprintf"value %s > bound %s"(namex)(nameincl))|Exclexcl->fail(Printf.sprintf"value %s >= bound %s"(namex)(nameexcl))endmoduleInfix=structlet(++)t1t2=combinet1t2end