src/lightspeed@framework@endpoint.erl

-module(lightspeed@framework@endpoint).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/lightspeed/framework/endpoint.gleam").
-export([new/2, pipe/2, get_controller/3, get_controller_indexed/3, get_live/4, get_live_indexed/4, static/4, call/2, route_labels/1, static_labels/1]).
-export_type([route/0, static_asset/0, endpoint/0, dispatch/0]).

-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.

?MODULEDOC(" Endpoint + middleware pipeline with controller and live-route dispatch.\n").

-type route() :: {controller_route,
        lightspeed@framework@http:method(),
        binary(),
        fun((lightspeed@framework@http:conn()) -> lightspeed@framework@http:conn())} |
    {live_route,
        lightspeed@framework@http:method(),
        binary(),
        binary(),
        fun((lightspeed@framework@http:conn()) -> binary())}.

-type static_asset() :: {static_asset, binary(), binary(), binary()}.

-opaque endpoint() :: {endpoint,
        binary(),
        lightspeed@transport@contract:auth_hook(),
        list(fun((lightspeed@framework@http:conn()) -> lightspeed@framework@http:conn())),
        list(route()),
        list(static_asset())}.

-type dispatch() :: {controller_dispatch,
        fun((lightspeed@framework@http:conn()) -> lightspeed@framework@http:conn()),
        list(lightspeed@framework@http:route_param())} |
    {live_dispatch,
        binary(),
        fun((lightspeed@framework@http:conn()) -> binary()),
        list(lightspeed@framework@http:route_param())}.

-file("src/lightspeed/framework/endpoint.gleam", 58).
?DOC(" Create an endpoint.\n").
-spec new(lightspeed@transport@contract:auth_hook(), binary()) -> endpoint().
new(Auth_hook, Websocket_path) ->
    {endpoint, Websocket_path, Auth_hook, [], [], []}.

-file("src/lightspeed/framework/endpoint.gleam", 69).
?DOC(" Register one middleware in endpoint order.\n").
-spec pipe(
    endpoint(),
    fun((lightspeed@framework@http:conn()) -> lightspeed@framework@http:conn())
) -> endpoint().
pipe(Endpoint, Middleware) ->
    {endpoint,
        erlang:element(2, Endpoint),
        erlang:element(3, Endpoint),
        [Middleware | erlang:element(4, Endpoint)],
        erlang:element(5, Endpoint),
        erlang:element(6, Endpoint)}.

-file("src/lightspeed/framework/endpoint.gleam", 74).
?DOC(" Register a GET controller route from a verified route helper.\n").
-spec get_controller(
    endpoint(),
    lightspeed@framework@verified_routes:verified_route(any()),
    fun((lightspeed@framework@http:conn()) -> lightspeed@framework@http:conn())
) -> endpoint().
get_controller(Endpoint, Route, Run) ->
    Pattern = lightspeed@framework@verified_routes:pattern(Route),
    {endpoint,
        erlang:element(2, Endpoint),
        erlang:element(3, Endpoint),
        erlang:element(4, Endpoint),
        [{controller_route, get, Pattern, Run} | erlang:element(5, Endpoint)],
        erlang:element(6, Endpoint)}.

-file("src/lightspeed/framework/endpoint.gleam", 87).
?DOC(" Register a GET controller route from one router-indexed verified route.\n").
-spec get_controller_indexed(
    endpoint(),
    lightspeed@framework@verified_routes:indexed_route(any(), lightspeed@framework@verified_routes:get_tag()),
    fun((lightspeed@framework@http:conn()) -> lightspeed@framework@http:conn())
) -> endpoint().
get_controller_indexed(Endpoint, Route, Run) ->
    get_controller(
        Endpoint,
        lightspeed@framework@verified_routes:as_verified_route(Route),
        Run
    ).

-file("src/lightspeed/framework/endpoint.gleam", 96).
?DOC(" Register a GET live route from a verified route helper.\n").
-spec get_live(
    endpoint(),
    lightspeed@framework@verified_routes:verified_route(any()),
    binary(),
    fun((lightspeed@framework@http:conn()) -> binary())
) -> endpoint().
get_live(Endpoint, Route, View_id, Render) ->
    Pattern = lightspeed@framework@verified_routes:pattern(Route),
    {endpoint,
        erlang:element(2, Endpoint),
        erlang:element(3, Endpoint),
        erlang:element(4, Endpoint),
        [{live_route, get, Pattern, View_id, Render} |
            erlang:element(5, Endpoint)],
        erlang:element(6, Endpoint)}.

-file("src/lightspeed/framework/endpoint.gleam", 115).
?DOC(" Register a GET live route from one router-indexed verified route.\n").
-spec get_live_indexed(
    endpoint(),
    lightspeed@framework@verified_routes:indexed_route(any(), lightspeed@framework@verified_routes:get_tag()),
    binary(),
    fun((lightspeed@framework@http:conn()) -> binary())
) -> endpoint().
get_live_indexed(Endpoint, Route, View_id, Render) ->
    get_live(
        Endpoint,
        lightspeed@framework@verified_routes:as_verified_route(Route),
        View_id,
        Render
    ).

-file("src/lightspeed/framework/endpoint.gleam", 420).
-spec join_graphemes(list(binary())) -> binary().
join_graphemes(Graphemes) ->
    case Graphemes of
        [] ->
            <<""/utf8>>;

        [Grapheme | Rest] ->
            <<Grapheme/binary, (join_graphemes(Rest))/binary>>
    end.

-file("src/lightspeed/framework/endpoint.gleam", 413).
-spec drop_last(binary()) -> binary().
drop_last(Path) ->
    case lists:reverse(gleam@string:to_graphemes(Path)) of
        [] ->
            <<""/utf8>>;

        [_ | Rest] ->
            _pipe = Rest,
            _pipe@1 = lists:reverse(_pipe),
            join_graphemes(_pipe@1)
    end.

-file("src/lightspeed/framework/endpoint.gleam", 406).
-spec ends_with_slash(binary()) -> boolean().
ends_with_slash(Path) ->
    case lists:reverse(gleam@string:to_graphemes(Path)) of
        [<<"/"/utf8>> | _] ->
            true;

        _ ->
            false
    end.

-file("src/lightspeed/framework/endpoint.gleam", 378).
-spec trim_trailing_slashes(binary()) -> binary().
trim_trailing_slashes(Path) ->
    case Path of
        <<"/"/utf8>> ->
            <<"/"/utf8>>;

        _ ->
            case ends_with_slash(Path) of
                true ->
                    trim_trailing_slashes(drop_last(Path));

                false ->
                    Path
            end
    end.

-file("src/lightspeed/framework/endpoint.gleam", 364).
-spec normalize_path(binary()) -> binary().
normalize_path(Path) ->
    No_query = case gleam@string:split(Path, <<"?"/utf8>>) of
        [First | _] ->
            First;

        [] ->
            Path
    end,
    Trimmed = trim_trailing_slashes(No_query),
    case Trimmed of
        <<""/utf8>> ->
            <<"/"/utf8>>;

        _ ->
            Trimmed
    end.

-file("src/lightspeed/framework/endpoint.gleam", 125).
?DOC(" Register one static asset.\n").
-spec static(endpoint(), binary(), binary(), binary()) -> endpoint().
static(Endpoint, Path, Content_type, Body) ->
    {endpoint,
        erlang:element(2, Endpoint),
        erlang:element(3, Endpoint),
        erlang:element(4, Endpoint),
        erlang:element(5, Endpoint),
        [{static_asset, normalize_path(Path), Content_type, Body} |
            erlang:element(6, Endpoint)]}.

-file("src/lightspeed/framework/endpoint.gleam", 267).
-spec render_live(
    lightspeed@framework@http:conn(),
    endpoint(),
    binary(),
    fun((lightspeed@framework@http:conn()) -> binary())
) -> lightspeed@framework@http:conn().
render_live(Conn, Endpoint, View_id, Render) ->
    Request = {html_request,
        lightspeed@framework@http:request_session_id(Conn),
        normalize_path(lightspeed@framework@http:request_path(Conn)),
        lightspeed@framework@http:request_csrf_token(Conn),
        lightspeed@framework@http:request_origin(Conn)},
    case lightspeed@transport@wisp_html:render_initial(
        Request,
        Render(Conn),
        erlang:element(2, Endpoint),
        erlang:element(3, Endpoint)
    ) of
        {ok, Response} ->
            _pipe = Conn,
            _pipe@1 = lightspeed@framework@http:send(
                _pipe,
                lightspeed@transport@wisp_html:status(Response),
                <<"text/html; charset=utf-8"/utf8>>,
                lightspeed@transport@wisp_html:body(Response)
            ),
            lightspeed@framework@http:put_header(
                _pipe@1,
                <<"x-lightspeed-view-id"/utf8>>,
                View_id
            );

        {error, Error} ->
            lightspeed@framework@controller:internal_error(
                Conn,
                lightspeed@transport@contract:error_to_string(Error)
            )
    end.

-file("src/lightspeed/framework/endpoint.gleam", 352).
-spec route_param_name(binary()) -> gleam@option:option(binary()).
route_param_name(Segment) ->
    case gleam@string:to_graphemes(Segment) of
        [<<":"/utf8>> | Rest] ->
            case Rest of
                [] ->
                    none;

                _ ->
                    {some, join_graphemes(Rest)}
            end;

        _ ->
            none
    end.

-file("src/lightspeed/framework/endpoint.gleam", 325).
-spec match_segments(
    list(binary()),
    list(binary()),
    list(lightspeed@framework@http:route_param())
) -> gleam@option:option(list(lightspeed@framework@http:route_param())).
match_segments(Pattern_segments, Path_segments, Captures_rev) ->
    case {Pattern_segments, Path_segments} of
        {[], []} ->
            {some, lists:reverse(Captures_rev)};

        {[], _} ->
            none;

        {_, []} ->
            none;

        {[Pattern_segment | Pattern_rest], [Path_segment | Path_rest]} ->
            case route_param_name(Pattern_segment) of
                {some, Name} ->
                    match_segments(
                        Pattern_rest,
                        Path_rest,
                        [{route_param, Name, Path_segment} | Captures_rev]
                    );

                none ->
                    case Pattern_segment =:= Path_segment of
                        true ->
                            match_segments(
                                Pattern_rest,
                                Path_rest,
                                Captures_rev
                            );

                        false ->
                            none
                    end
            end
    end.

-file("src/lightspeed/framework/endpoint.gleam", 395).
-spec filter_empty(list(binary()), list(binary())) -> list(binary()).
filter_empty(Values, Kept_rev) ->
    case Values of
        [] ->
            lists:reverse(Kept_rev);

        [Value | Rest] ->
            case Value =:= <<""/utf8>> of
                true ->
                    filter_empty(Rest, Kept_rev);

                false ->
                    filter_empty(Rest, [Value | Kept_rev])
            end
    end.

-file("src/lightspeed/framework/endpoint.gleam", 389).
-spec segments(binary()) -> list(binary()).
segments(Path) ->
    _pipe = Path,
    _pipe@1 = gleam@string:split(_pipe, <<"/"/utf8>>),
    filter_empty(_pipe@1, []).

-file("src/lightspeed/framework/endpoint.gleam", 316).
-spec match_pattern(binary(), binary()) -> gleam@option:option(list(lightspeed@framework@http:route_param())).
match_pattern(Pattern, Path) ->
    Pattern_segments = segments(Pattern),
    Path_segments = segments(Path),
    match_segments(Pattern_segments, Path_segments, []).

-file("src/lightspeed/framework/endpoint.gleam", 312).
-spec method_matches(
    lightspeed@framework@http:method(),
    lightspeed@framework@http:method()
) -> boolean().
method_matches(Expected, Actual) ->
    lightspeed@framework@http:method_label(Expected) =:= lightspeed@framework@http:method_label(
        Actual
    ).

-file("src/lightspeed/framework/endpoint.gleam", 230).
-spec find_dispatch_loop(
    list(route()),
    lightspeed@framework@http:method(),
    binary()
) -> gleam@option:option(dispatch()).
find_dispatch_loop(Routes, Method, Path) ->
    case Routes of
        [] ->
            none;

        [Route | Rest] ->
            case Route of
                {controller_route, Route_method, Pattern, Run} ->
                    case {method_matches(Route_method, Method),
                        match_pattern(Pattern, Path)} of
                        {true, {some, Params}} ->
                            {some, {controller_dispatch, Run, Params}};

                        {_, _} ->
                            find_dispatch_loop(Rest, Method, Path)
                    end;

                {live_route, Route_method@1, Pattern@1, View_id, Render} ->
                    case {method_matches(Route_method@1, Method),
                        match_pattern(Pattern@1, Path)} of
                        {true, {some, Params@1}} ->
                            {some, {live_dispatch, View_id, Render, Params@1}};

                        {_, _} ->
                            find_dispatch_loop(Rest, Method, Path)
                    end
            end
    end.

-file("src/lightspeed/framework/endpoint.gleam", 224).
-spec find_dispatch(endpoint(), lightspeed@framework@http:conn()) -> gleam@option:option(dispatch()).
find_dispatch(Endpoint, Conn) ->
    Method = lightspeed@framework@http:request_method(Conn),
    Path = normalize_path(lightspeed@framework@http:request_path(Conn)),
    _pipe = erlang:element(5, Endpoint),
    _pipe@1 = lists:reverse(_pipe),
    find_dispatch_loop(_pipe@1, Method, Path).

-file("src/lightspeed/framework/endpoint.gleam", 210).
-spec find_static(list(static_asset()), binary()) -> gleam@option:option(static_asset()).
find_static(Static_assets, Path) ->
    case Static_assets of
        [] ->
            none;

        [Asset | Rest] ->
            case erlang:element(2, Asset) =:= Path of
                true ->
                    {some, Asset};

                false ->
                    find_static(Rest, Path)
            end
    end.

-file("src/lightspeed/framework/endpoint.gleam", 196).
-spec serve_static(endpoint(), lightspeed@framework@http:conn()) -> gleam@option:option(lightspeed@framework@http:conn()).
serve_static(Endpoint, Conn) ->
    case lightspeed@framework@http:request_method(Conn) of
        get ->
            Path = normalize_path(lightspeed@framework@http:request_path(Conn)),
            case find_static(erlang:element(6, Endpoint), Path) of
                {some, Asset} ->
                    {some,
                        lightspeed@framework@http:send(
                            Conn,
                            200,
                            erlang:element(3, Asset),
                            erlang:element(4, Asset)
                        )};

                none ->
                    none
            end;

        _ ->
            none
    end.

-file("src/lightspeed/framework/endpoint.gleam", 185).
-spec run_middleware(
    lightspeed@framework@http:conn(),
    list(fun((lightspeed@framework@http:conn()) -> lightspeed@framework@http:conn()))
) -> lightspeed@framework@http:conn().
run_middleware(Conn, Reversed) ->
    case Reversed of
        [] ->
            Conn;

        [Middleware | Rest] ->
            case lightspeed@framework@http:halted(Conn) of
                true ->
                    Conn;

                false ->
                    run_middleware(Middleware(Conn), Rest)
            end
    end.

-file("src/lightspeed/framework/endpoint.gleam", 142).
?DOC(" Handle one request through middleware and dispatch tables.\n").
-spec call(endpoint(), lightspeed@framework@http:request()) -> lightspeed@framework@http:response().
call(Endpoint, Request) ->
    Conn = run_middleware(
        begin
            _pipe = Request,
            lightspeed@framework@http:from_request(_pipe)
        end,
        erlang:element(4, Endpoint)
    ),
    case lightspeed@framework@http:halted(Conn) of
        true ->
            lightspeed@framework@http:to_response(Conn);

        false ->
            case serve_static(Endpoint, Conn) of
                {some, Served} ->
                    lightspeed@framework@http:to_response(Served);

                none ->
                    case find_dispatch(Endpoint, Conn) of
                        {some, {controller_dispatch, Run, Params}} ->
                            _pipe@1 = Conn,
                            _pipe@2 = lightspeed@framework@http:with_route_params(
                                _pipe@1,
                                Params
                            ),
                            _pipe@3 = Run(_pipe@2),
                            lightspeed@framework@http:to_response(_pipe@3);

                        {some, {live_dispatch, View_id, Render, Params@1}} ->
                            _pipe@4 = Conn,
                            _pipe@5 = lightspeed@framework@http:with_route_params(
                                _pipe@4,
                                Params@1
                            ),
                            _pipe@6 = render_live(
                                _pipe@5,
                                Endpoint,
                                View_id,
                                Render
                            ),
                            lightspeed@framework@http:to_response(_pipe@6);

                        none ->
                            _pipe@7 = Conn,
                            _pipe@8 = lightspeed@framework@controller:not_found(
                                _pipe@7
                            ),
                            lightspeed@framework@http:to_response(_pipe@8)
                    end
            end
    end.

-file("src/lightspeed/framework/endpoint.gleam", 303).
-spec route_label(route()) -> binary().
route_label(Route) ->
    case Route of
        {controller_route, Method, Pattern, _} ->
            <<<<<<"controller:"/utf8,
                        (lightspeed@framework@http:method_label(Method))/binary>>/binary,
                    ":"/utf8>>/binary,
                Pattern/binary>>;

        {live_route, Method@1, Pattern@1, View_id, _} ->
            <<<<<<<<<<"live:"/utf8,
                                (lightspeed@framework@http:method_label(
                                    Method@1
                                ))/binary>>/binary,
                            ":"/utf8>>/binary,
                        Pattern@1/binary>>/binary,
                    ":"/utf8>>/binary,
                View_id/binary>>
    end.

-file("src/lightspeed/framework/endpoint.gleam", 172).
?DOC(" Stable endpoint route table labels.\n").
-spec route_labels(endpoint()) -> list(binary()).
route_labels(Endpoint) ->
    _pipe = erlang:element(5, Endpoint),
    _pipe@1 = lists:reverse(_pipe),
    gleam@list:map(_pipe@1, fun route_label/1).

-file("src/lightspeed/framework/endpoint.gleam", 179).
?DOC(" Stable static table labels.\n").
-spec static_labels(endpoint()) -> list(binary()).
static_labels(Endpoint) ->
    _pipe = erlang:element(6, Endpoint),
    _pipe@1 = lists:reverse(_pipe),
    gleam@list:map(
        _pipe@1,
        fun(Asset) -> <<"static:"/utf8, (erlang:element(2, Asset))/binary>> end
    ).