-module(howdy@url_parser). -compile(no_auto_import). -export([parse/2]). -export_type([segment_error/0, url_segment/0]). -type segment_error() :: non_matching_url. -type url_segment() :: {matching_segment, binary(), binary()} | {matching_template_typed_segment, binary(), binary(), binary(), gleam@dynamic:dynamic()} | {wildcard_segment, binary(), binary(), binary()} | non_matching_segment. -spec parse(binary(), binary()) -> {ok, list(url_segment())} | {error, segment_error()}. parse(Template, Path) -> case gleam@string:ends_with(Template, <<"*"/utf8>>) of true -> wildcard_parse(Template, Path); false -> template_parse(Template, Path) end. -spec template_parse(binary(), binary()) -> {ok, list(url_segment())} | {error, segment_error()}. template_parse(Template, Path) -> Template_url = gleam@uri:path_segments(Template), Path_url = gleam@uri:path_segments(Path), _pipe = gleam@list:strict_zip(Template_url, Path_url), _pipe@1 = gleam@result:replace_error(_pipe, non_matching_url), _pipe@2 = gleam@result:map(_pipe@1, fun url_parts/1), gleam@result:flatten(_pipe@2). -spec wildcard_parse(binary(), binary()) -> {ok, list(url_segment())} | {error, segment_error()}. wildcard_parse(Template, Path) -> Template_url = gleam@uri:path_segments(Template), Path_url = gleam@uri:path_segments(Path), _pipe = gleam@list:zip(Template_url, Path_url), wildcard_parts(_pipe, Template, Path). -spec wildcard_parts(list({binary(), binary()}), binary(), binary()) -> {ok, list(url_segment())} | {error, segment_error()}. wildcard_parts(Url_parts, Template, Path) -> _pipe = gleam@list:map( Url_parts, fun(Part) -> case {is_wild_card(erlang:element(1, Part)), template_matches_path(Part)} of {true, _@1} -> {wildcard_segment, erlang:element(1, Part), erlang:element(2, Part), wildcard_join_paths(Template, Path)}; {_@2, true} -> {matching_segment, erlang:element(1, Part), erlang:element(2, Part)}; {_@3, _@4} -> non_matching_segment end end ), has_nonmatching_segments(_pipe). -spec wildcard_join_paths(binary(), binary()) -> binary(). wildcard_join_paths(Template, Path) -> Striped_template = gleam@string:drop_right(Template, 1), case gleam@string:starts_with(Path, Striped_template) of true -> gleam@string:drop_left(Path, gleam@string:length(Striped_template)); false -> Path end. -spec is_wild_card(binary()) -> boolean(). is_wild_card(Template_segment) -> Template_segment =:= <<"*"/utf8>>. -spec url_parts(list({binary(), binary()})) -> {ok, list(url_segment())} | {error, segment_error()}. url_parts(Url_parts) -> _pipe = gleam@list:map(Url_parts, fun convert_to_url_segment/1), has_nonmatching_segments(_pipe). -spec convert_to_url_segment({binary(), binary()}) -> url_segment(). convert_to_url_segment(Url_part) -> case {is_templated_path(Url_part), template_matches_path(Url_part)} of {true, false} -> process_templated_url_segment(Url_part); {false, true} -> {matching_segment, erlang:element(1, Url_part), erlang:element(2, Url_part)}; {_@1, _@2} -> non_matching_segment end. -spec is_templated_path({binary(), binary()}) -> boolean(). is_templated_path(Key_value) -> gleam@string:starts_with(erlang:element(1, Key_value), <<"{"/utf8>>) andalso gleam@string:ends_with( erlang:element(1, Key_value), <<"}"/utf8>> ). -spec template_matches_path({binary(), binary()}) -> boolean(). template_matches_path(Key_value) -> gleam@string:lowercase(erlang:element(1, Key_value)) =:= gleam@string:lowercase( erlang:element(2, Key_value) ). -spec process_templated_url_segment({binary(), binary()}) -> url_segment(). process_templated_url_segment(Url_part) -> case do_process_templated_url_segment(Url_part) of {ok, Result} -> Result; {error, _@1} -> non_matching_segment end. -spec do_process_templated_url_segment({binary(), binary()}) -> {ok, url_segment()} | {error, nil}. do_process_templated_url_segment(Url_part) -> {Template_url, Path_url} = Url_part, _pipe = Template_url, _pipe@1 = gleam@string:drop_left(_pipe, 1), _pipe@2 = gleam@string:drop_right(_pipe@1, 1), _pipe@3 = gleam@string:split_once(_pipe@2, <<":"/utf8>>), _pipe@4 = gleam@result:map( _pipe@3, fun(_capture) -> process_template(_capture, Path_url) end ), gleam@result:map( _pipe@4, fun(_capture@1) -> convert_to_template_url_segment(_capture@1, Template_url, Path_url) end ). -spec process_template({binary(), binary()}, binary()) -> {gleam@option:option(gleam@dynamic:dynamic()), binary()}. process_template(Key_value, Input) -> {Name, Template_type} = Key_value, Result = case gleam@string:lowercase(Template_type) of <<"string"/utf8>> -> process_string(Input); <<"int"/utf8>> -> process_int(Input); <<"float"/utf8>> -> process_float(Input); <<"uuid"/utf8>> -> process_uuid(Input); _@1 -> none end, {Result, Name}. -spec convert_to_template_url_segment( {gleam@option:option(gleam@dynamic:dynamic()), binary()}, binary(), binary() ) -> url_segment(). convert_to_template_url_segment(Result, Template_segment, Path_segment) -> {Processed, Name} = Result, case Processed of {some, Value} -> {matching_template_typed_segment, Template_segment, Path_segment, Name, Value}; none -> non_matching_segment end. -spec process_string(binary()) -> gleam@option:option(gleam@dynamic:dynamic()). process_string(Value) -> _pipe = gleam@string:to_option(Value), gleam@option:map(_pipe, fun(Str) -> gleam@dynamic:from(Str) end). -spec process_int(binary()) -> gleam@option:option(gleam@dynamic:dynamic()). process_int(Value) -> _pipe = gleam@int:parse(Value), _pipe@1 = gleam@option:from_result(_pipe), gleam@option:map(_pipe@1, fun(Num) -> gleam@dynamic:from(Num) end). -spec process_float(binary()) -> gleam@option:option(gleam@dynamic:dynamic()). process_float(Value) -> _pipe = gleam@float:parse(Value), _pipe@1 = gleam@option:from_result(_pipe), gleam@option:map(_pipe@1, fun(Num) -> gleam@dynamic:from(Num) end). -spec process_uuid(binary()) -> gleam@option:option(gleam@dynamic:dynamic()). process_uuid(Value) -> _pipe = howdy@uuid:from_string(Value), _pipe@1 = gleam@option:from_result(_pipe), gleam@option:map(_pipe@1, fun(_) -> gleam@dynamic:from(Value) end). -spec has_nonmatching_segments(list(url_segment())) -> {ok, list(url_segment())} | {error, segment_error()}. has_nonmatching_segments(Segments) -> case gleam@list:contains(Segments, non_matching_segment) of true -> {error, non_matching_url}; false -> {ok, Segments} end.