123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185(*-------------------------------------------------------------------------
* Copyright (c) 2021 Bikal Gurung. All rights reserved.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
*-------------------------------------------------------------------------*)openPpxlibmoduleAst_builder=Ast_builder.Defaultlet(let*)rf=Result.bindrflet(>>=)=(let*)letrecmake_route~loc~path:_wtr=letwtr=String.trimwtrinletmethods,uri=lettokens=String.split_on_char';'wtr|>List.mapString.trim|>List.filter(funs->not(String.equal""s))inifList.lengthtokens!=2thenLocation.raise_errorf~loc"Invalid wtr: %s. Valid wtr is: [HTTP methods separated by comma (,)] \
; [URI]"wtrelse(List.nthtokens0,List.nthtokens1)in(let*uri=parse_uriuriinlet*query_components=parse_query_tokensuriinlet*path_components=parse_path_tokensuriinvalidate_tokens(path_components@query_components))|>function|Okuri_tokens->letmethods'=to_methodsmethodsinleturis=(ifList.lengthmethods'=0then[uri_tokens]elseList.map(funm->m::uri_tokens)methods')|>make_uris~locin[%exprWtr.Private.route[%euris]]|Errormsg->Location.raise_errorf~loc"wtr: %s"msgandmake_uris~loc=function|[]->[%expr[]]|uri_toks::l->[%expr[%emake_uri~locuri_toks]::[%emake_uris~locl]]andto_methodsmethods_str=String.split_on_char','methods_str|>List.filter_map(funs->lets=String.trimsinifString.lengths>0thenSome("^^"^String.uppercase_asciis)elseNone)andparse_uriwtr=letwtr=String.trimwtrinifString.lengthwtr>0thenOk(Uri.of_stringwtr)elseError"Empty uri path specification"andparse_query_tokensuri=letexceptionEofstringintryUri.queryuri|>List.map(fun(k,v)->ifList.lengthv!=1thenraise(E(Printf.sprintf"Invalid query specification for key: %s"k))else[k;List.hdv])|>List.concat|>Result.okwithEmsg->Errormsgandparse_path_tokensuri=Ok(Uri.pathuri|>String.split_on_char'/')andvalidate_tokenstokens=letvalidate_starttokens=matchList.hdtokenswith|""->Ok(List.tltokens)|_|(exception_)->Error"Uri path specification must start with '/'"inletvalidate_end_slashpath=let_,l2=split_on(funx->String.equal""x)pathinifList.lengthl2>0thenError"Invalid uri path specification. No tokens allowed after trailing '/' \
token"elseOkpathinletvalidate_full_splatpath=let_,l2=split_on(funx->String.equal"**"x)pathinifList.lengthl2>0thenError"Invalid uri path specification. No tokens allowed after full splat \
(**) token"elseOkpathinvalidate_starttokens>>=validate_end_slash>>=validate_full_splatandfindifl=letrecloopn=function|[]->None|x::t->iffxthenSomenelseloop(n+1)tinloop0landstarts_with~prefixs=letlen_s=String.lengthsandlen_pre=String.lengthprefixinletrecauxi=ifi=len_prethentrueelseifString.unsafe_getsi<>String.unsafe_getprefixithenfalseelseaux(i+1)inlen_s>=len_pre&&aux0andsplit_onfl=matchfindiflwith|Somen->(List.filteri(funi_->i<n)l,List.filteri(funi_->i>n)l)|None->(l,[])andmake_uri~loc=function|[]->[%exprWtr.Private.nil]|[""]->[%exprWtr.Private.trailing_slash]|["**"]->[%exprWtr.Private.full_splat]|"*"::components->[%exprWtr.Private.decoderWtr.Private.string[%emake_uri~loccomponents]]|comp::componentswhenChar.equalcomp.[0]':'->((* Decoders *)letcomp=String.subcomp1(String.lengthcomp-1)inmatchcompwith|"int"->[%exprWtr.Private.decoderWtr.Private.int[%emake_uri~loccomponents]]|"int32"->[%exprWtr.Private.decoderWtr.Private.int32[%emake_uri~loccomponents]]|"int64"->[%exprWtr.Private.decoderWtr.Private.int64[%emake_uri~loccomponents]]|"float"->[%exprWtr.Private.decoderWtr.Private.float[%emake_uri~loccomponents]]|"string"->[%exprWtr.Private.decoderWtr.Private.string[%emake_uri~loccomponents]]|"bool"->[%exprWtr.Private.decoderWtr.Private.bool[%emake_uri~loccomponents]]|custom_argwhencapitalizedcustom_arg->letlongident_loc={txt=Longident.parse(custom_arg^".t");loc}in[%exprWtr.Private.decoder[%eAst_builder.pexp_ident~loclongident_loc][%emake_uri~loccomponents]]|x->Location.raise_errorf~loc"wtr: Invalid custom argument name '%s'. Custom argument component \
name must be a valid module name."x)|comp::componentswhenstarts_with~prefix:"^^"comp->(* Methods *)letmethod'=String.(subcomp2(lengthcomp-2))inletmeth_expr=[%exprWtr.method'[%eAst_builder.estring~locmethod']]in[%exprWtr.Private.method'[%emeth_expr][%emake_uri~loccomponents]]|comp::components->[%exprWtr.Private.lit[%eAst_builder.estring~loccomp][%emake_uri~loccomponents]]andcapitalizeds=Char.(uppercase_asciis.[0]|>equals.[0])letppx_name="wtr"letext=Extension.declareppx_nameExtension.Context.ExpressionAst_pattern.(single_expr_payload(estring__))make_routelet()=Driver.register_transformationppx_name~extensions:[ext]