123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151openCoreopenS.Route_exceptions(*TODO: add an mli file. *)letslash=Re2.create_exn"/"letsegmentspath=Re2.splitslashpath|>List.filter~f:(funs->s<>"")lettrailing_segmentspath=Re2.splitslashpath|>function|""::tl->tl|segments->segmentsmodulePath_segment=structletrule=Re2.create_exn{|(?P<static>[^<]*)<(?:(?P<converter>[a-zA-Z_][a-zA-Z0-9_]*)(?:\((?P<args>.*?)\))?\:)?(?P<variable>[a-zA-Z_][a-zA-Z0-9_]*)>|}typet=(* TODO add coverter option to the dynamic case *)|Dynamicof{variable:string}|Staticofstring[@@derivingsexp,compare,hash]letextract_componentsm=letv,c,s=Re2.Match.get~sub:(`Name"variable")m,Re2.Match.get~sub:(`Name"converter")m,Re2.Match.get~sub:(`Name"static")minlettemplate_parts=matchv,cwith|Some_variable,Some_converter->(*(Dynamic_with_converter { variable=variable;
converter=converter})::[] *)(* TODO: support converters *)failwith"Converters not supported yet"|Somevariable,None->(Dynamic{variable=variable})::[]|None,Some_converter->failwith"Can't have a converter without a variable"|None,None->[]inOption.value_maps~default:template_parts~f:(funstatic->letsegments=segmentsstatic|>List.map~f:(funsegment->Staticsegment)inList.appendsegmentstemplate_parts)letvariable_name=function|Dynamic{variable;_}->Somevariable|Static_->Noneletpath_segments_and_variable_namestemplate=letms=Re2.get_matches_exnruletemplateinletps=List.concat_mapms~f:extract_componentsinletvars=List.fold_leftps~init:String.Set.empty~f:(funvssegment->matchvariable_namesegmentwith|Somev->ifnot(String.Set.memvsv)thenString.Set.addvsvelseInvalidRouteTemplate(sprintf"Duplicate variable name found in template %s"template)|>raise|None->vs)inletend_match=List.lastms|>Option.map~f:(Re2.Match.get_pos_exn~sub:(`Index0))|>Option.value_map~default:0~f:(fun(start,offset)->start+offset)inifend_match<String.lengthtemplatethenletremainder=letlen=(String.lengthtemplate)-end_matchinString.sub~pos:end_match~lentemplateinifString.containsremainder'<'||String.containsremainder'>'thenInvalidRouteTemplate(sprintf"Malformed variable in url template %s"template)|>raiseelseList.appendps(trailing_segmentsremainder|>List.map~f:(funs->Statics)),varselseps,varsendmodulePath=structincludePath_segmentincludeHashable.Make(Path_segment)endtype'apath_trie={mutableis_terminal:'aoption;mutableis_dynamic_path:(string*'apath_trie)option;map:'apath_trieString.Table.t}[@@derivingsexp]letempty()={is_terminal=None;is_dynamic_path=None;map=String.Table.create()}letinserttsegmentsvalue=letrechelpertsegments=matchsegmentswith|[]->beginmatcht.is_terminalwith|Some_->`Duplicate|None->t.is_terminal<-Somevalue;`Okend|Path.Dynamic{variable}::tl->beginmatcht.is_dynamic_pathwith|Some(_,t')->helpert'tl|None->lett'=empty()inbeginmatchhelpert'tlwith|`Ok->t.is_dynamic_path<-Some(variable,t');`Ok|`Duplicate->`Duplicateendend|Path.Staticstatic::tl->beginmatchHashtbl.findt.mapstaticwith|Somet'->helpert'tl|None->lett'=empty()inbeginmatchhelpert'tlwith|`Ok->Hashtbl.add_exnt.map~key:static~data:t';`Ok|`Duplicate->`Duplicateendendinhelpertsegments(* Convert string to list of segments *)letinsert_pathtriepath=letpath_segments,_=Path_segment.path_segments_and_variable_namespathininserttriepath_segmentsletmatchestpath=letsegments=trailing_segmentspathinletrechelpertvssegments=matchsegmentswith|[]->Option.mapt.is_terminal~f:(funvalue->vs,value)|segment::tl->beginmatchHashtbl.findt.mapsegmentwith|Somet'->helpert'vstl|None->Option.bindt.is_dynamic_path~f:(fun(v,t')->Hashtbl.add_exnvs~key:v~data:segment;helpert'vstl)endinhelpert(String.Table.create())segments