Skip to main content

src/proute@validate@pages.erl

-module(proute@validate@pages).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/proute/validate/pages.gleam").
-export([validate_mount/1, describe_error/1]).
-export_type([validation_error/0, page_module/0, module_shape/0]).

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

-type page_module() :: {page_module,
        proute@discover:page_route(),
        boolean(),
        boolean()}.

-type module_shape() :: {module_shape,
        boolean(),
        boolean(),
        list(integer()),
        list(integer()),
        list(integer()),
        {ok, integer()} | {error, nil}}.

-file("src/proute/validate/pages.gleam", 334).
-spec count_commas(list(binary()), integer(), integer()) -> integer().
count_commas(Chars, Depth, Commas) ->
    case Chars of
        [] ->
            Commas;

        [Char | Rest] ->
            case Char of
                <<"("/utf8>> ->
                    count_commas(Rest, Depth + 1, Commas);

                <<"["/utf8>> ->
                    count_commas(Rest, Depth + 1, Commas);

                <<"{"/utf8>> ->
                    count_commas(Rest, Depth + 1, Commas);

                <<")"/utf8>> ->
                    count_commas(Rest, Depth - 1, Commas);

                <<"]"/utf8>> ->
                    count_commas(Rest, Depth - 1, Commas);

                <<"}"/utf8>> ->
                    count_commas(Rest, Depth - 1, Commas);

                <<","/utf8>> ->
                    case Depth of
                        0 ->
                            count_commas(Rest, Depth, Commas + 1);

                        _ ->
                            count_commas(Rest, Depth, Commas)
                    end;

                _ ->
                    count_commas(Rest, Depth, Commas)
            end
    end.

-file("src/proute/validate/pages.gleam", 312).
-spec count_params(binary()) -> integer().
count_params(Params) ->
    Trimmed = gleam@string:trim(Params),
    case Trimmed of
        <<""/utf8>> ->
            0;

        _ ->
            case gleam_stdlib:string_ends_with(Trimmed, <<","/utf8>>) of
                true ->
                    _pipe = Trimmed,
                    _pipe@1 = gleam@string:drop_end(_pipe, 1),
                    _pipe@2 = gleam@string:to_graphemes(_pipe@1),
                    _pipe@3 = count_commas(_pipe@2, 0, 0),
                    (fun(Commas) -> Commas + 1 end)(_pipe@3);

                false ->
                    _pipe@4 = Trimmed,
                    _pipe@5 = gleam@string:to_graphemes(_pipe@4),
                    _pipe@6 = count_commas(_pipe@5, 0, 0),
                    (fun(Commas@1) -> Commas@1 + 1 end)(_pipe@6)
            end
    end.

-file("src/proute/validate/pages.gleam", 292).
-spec take_until_closing_paren(binary(), integer(), binary()) -> {ok, binary()} |
    {error, nil}.
take_until_closing_paren(Text, Depth, Built) ->
    case gleam_stdlib:string_pop_grapheme(Text) of
        {error, _} ->
            {error, nil};

        {ok, {Char, Rest}} ->
            case Char of
                <<"("/utf8>> ->
                    take_until_closing_paren(
                        Rest,
                        Depth + 1,
                        <<Built/binary, Char/binary>>
                    );

                <<")"/utf8>> ->
                    case Depth of
                        1 ->
                            {ok, Built};

                        _ ->
                            take_until_closing_paren(
                                Rest,
                                Depth - 1,
                                <<Built/binary, Char/binary>>
                            )
                    end;

                _ ->
                    take_until_closing_paren(
                        Rest,
                        Depth,
                        <<Built/binary, Char/binary>>
                    )
            end
    end.

-file("src/proute/validate/pages.gleam", 281).
-spec public_fn_params(binary(), binary()) -> {ok, binary()} | {error, nil}.
public_fn_params(Declaration, Name) ->
    Prefix = <<<<"pub fn "/utf8, Name/binary>>/binary, "("/utf8>>,
    gleam@result:'try'(
        case gleam_stdlib:string_starts_with(Declaration, Prefix) of
            true ->
                {ok,
                    gleam@string:drop_start(Declaration, string:length(Prefix))};

            false ->
                {error, nil}
        end,
        fun(Rest) -> take_until_closing_paren(Rest, 1, <<""/utf8>>) end
    ).

-file("src/proute/validate/pages.gleam", 273).
-spec public_fn_arities(list(binary()), binary()) -> list(integer()).
public_fn_arities(Declarations, Name) ->
    _pipe = Declarations,
    gleam@list:filter_map(
        _pipe,
        fun(Declaration) -> _pipe@1 = public_fn_params(Declaration, Name),
            gleam@result:map(_pipe@1, fun count_params/1) end
    ).

-file("src/proute/validate/pages.gleam", 264).
-spec public_fn_arity(list(binary()), binary()) -> {ok, integer()} |
    {error, nil}.
public_fn_arity(Declarations, Name) ->
    _pipe = Declarations,
    _pipe@1 = public_fn_arities(_pipe, Name),
    gleam@list:first(_pipe@1).

-file("src/proute/validate/pages.gleam", 254).
-spec has_public_type(list(binary()), binary()) -> boolean().
has_public_type(Declarations, Name) ->
    _pipe = Declarations,
    gleam@list:any(
        _pipe,
        fun(Declaration) ->
            (((Declaration =:= (<<"pub type "/utf8, Name/binary>>)) orelse gleam_stdlib:string_starts_with(
                Declaration,
                <<<<"pub type "/utf8, Name/binary>>/binary, " "/utf8>>
            ))
            orelse gleam_stdlib:string_starts_with(
                Declaration,
                <<<<"pub type "/utf8, Name/binary>>/binary, "{"/utf8>>
            ))
            orelse gleam_stdlib:string_starts_with(
                Declaration,
                <<<<"pub type "/utf8, Name/binary>>/binary, " {"/utf8>>
            )
        end
    ).

-file("src/proute/validate/pages.gleam", 402).
-spec declaration_complete(binary()) -> boolean().
declaration_complete(Declaration) ->
    (gleam_stdlib:string_starts_with(Declaration, <<"pub type "/utf8>>) orelse gleam_stdlib:contains_string(
        Declaration,
        <<"{"/utf8>>
    ))
    orelse gleam_stdlib:contains_string(Declaration, <<"->"/utf8>>).

-file("src/proute/validate/pages.gleam", 359).
-spec collect_declarations(list(binary()), list(binary()), binary()) -> list(binary()).
collect_declarations(Lines, Declarations, Current) ->
    case Lines of
        [] ->
            case Current of
                <<""/utf8>> ->
                    Declarations;

                _ ->
                    [gleam@string:trim(Current) | Declarations]
            end;

        [Line | Rest] ->
            Trimmed = gleam@string:trim(Line),
            case {Current,
                gleam_stdlib:string_starts_with(Trimmed, <<"pub type "/utf8>>),
                gleam_stdlib:string_starts_with(Trimmed, <<"pub fn "/utf8>>)} of
                {<<""/utf8>>, false, false} ->
                    collect_declarations(Rest, Declarations, <<""/utf8>>);

                {<<""/utf8>>, _, _} ->
                    case declaration_complete(Trimmed) of
                        true ->
                            collect_declarations(
                                Rest,
                                [Trimmed | Declarations],
                                <<""/utf8>>
                            );

                        false ->
                            collect_declarations(Rest, Declarations, Trimmed)
                    end;

                {_, _, _} ->
                    Next = <<<<Current/binary, " "/utf8>>/binary,
                        Trimmed/binary>>,
                    case declaration_complete(Next) of
                        true ->
                            collect_declarations(
                                Rest,
                                [gleam@string:trim(Next) | Declarations],
                                <<""/utf8>>
                            );

                        false ->
                            collect_declarations(Rest, Declarations, Next)
                    end
            end
    end.

-file("src/proute/validate/pages.gleam", 408).
-spec strip_comment(binary()) -> binary().
strip_comment(Line) ->
    case gleam@string:split_once(Line, <<"//"/utf8>>) of
        {ok, {Before, _}} ->
            Before;

        {error, _} ->
            Line
    end.

-file("src/proute/validate/pages.gleam", 351).
-spec declarations(binary()) -> list(binary()).
declarations(Source) ->
    _pipe = Source,
    _pipe@1 = gleam@string:split(_pipe, <<"\n"/utf8>>),
    _pipe@2 = gleam@list:map(_pipe@1, fun strip_comment/1),
    _pipe@3 = collect_declarations(_pipe@2, [], <<""/utf8>>),
    lists:reverse(_pipe@3).

-file("src/proute/validate/pages.gleam", 239).
-spec module_shape(binary()) -> module_shape().
module_shape(Source) ->
    Declarations = begin
        _pipe = Source,
        declarations(_pipe)
    end,
    {module_shape,
        has_public_type(Declarations, <<"Model"/utf8>>),
        has_public_type(Declarations, <<"Message"/utf8>>),
        public_fn_arities(Declarations, <<"init"/utf8>>),
        public_fn_arities(Declarations, <<"initial_model"/utf8>>),
        public_fn_arities(Declarations, <<"update"/utf8>>),
        public_fn_arity(Declarations, <<"view"/utf8>>)}.

-file("src/proute/validate/pages.gleam", 215).
-spec expected_init_arity(proute@discover:page_route()) -> integer().
expected_init_arity(Route) ->
    case erlang:element(6, Route) of
        [] ->
            2;

        _ ->
            3
    end.

-file("src/proute/validate/pages.gleam", 141).
-spec result_to_list({ok, HMU} | {error, any()}) -> list(HMU).
result_to_list(Result) ->
    case Result of
        {ok, Value} ->
            [Value];

        {error, _} ->
            []
    end.

-file("src/proute/validate/pages.gleam", 203).
-spec error(proute@discover:page_route(), binary(), binary()) -> validation_error().
error(Route, Item, Expected) ->
    {validation_error, erlang:element(7, Route), Item, Expected}.

-file("src/proute/validate/pages.gleam", 161).
-spec require_arity(
    list(validation_error()),
    list(integer()),
    integer(),
    proute@discover:page_route(),
    binary(),
    binary()
) -> list(validation_error()).
require_arity(Errors, Actual, Expected_arity, Route, Item, Expected) ->
    case gleam@list:contains(Actual, Expected_arity) of
        true ->
            Errors;

        false ->
            [error(Route, Item, Expected) | Errors]
    end.

-file("src/proute/validate/pages.gleam", 189).
-spec require_any_arity(
    list(validation_error()),
    list(integer()),
    list(integer()),
    proute@discover:page_route(),
    binary(),
    binary()
) -> list(validation_error()).
require_any_arity(Errors, Actual, Expected_arities, Route, Item, Expected) ->
    case gleam@list:any(
        Expected_arities,
        fun(Arity) -> gleam@list:contains(Actual, Arity) end
    ) of
        true ->
            Errors;

        false ->
            [error(Route, Item, Expected) | Errors]
    end.

-file("src/proute/validate/pages.gleam", 231).
-spec expected_initial_model_shape(proute@discover:page_route()) -> binary().
expected_initial_model_shape(Route) ->
    case erlang:element(6, Route) of
        [] ->
            <<"pub fn initial_model(page_shared_state, query_params) -> Model"/utf8>>;

        _ ->
            <<"pub fn initial_model(page_shared_state, route_params, query_params) -> Model"/utf8>>
    end.

-file("src/proute/validate/pages.gleam", 222).
-spec expected_optional_init_shape(proute@discover:page_route()) -> binary().
expected_optional_init_shape(Route) ->
    case erlang:element(6, Route) of
        [] ->
            <<"optional pub fn init(page_shared_state, query_params) -> #(Model, Effect(Message))"/utf8>>;

        _ ->
            <<"optional pub fn init(page_shared_state, route_params, query_params) -> #(Model, Effect(Message))"/utf8>>
    end.

-file("src/proute/validate/pages.gleam", 175).
-spec require_optional_arity(
    list(validation_error()),
    list(integer()),
    integer(),
    proute@discover:page_route(),
    binary(),
    binary()
) -> list(validation_error()).
require_optional_arity(Errors, Actual, Expected_arity, Route, Item, Expected) ->
    case Actual of
        [] ->
            Errors;

        _ ->
            require_arity(Errors, Actual, Expected_arity, Route, Item, Expected)
    end.

-file("src/proute/validate/pages.gleam", 148).
-spec require(
    list(validation_error()),
    boolean(),
    proute@discover:page_route(),
    binary(),
    binary()
) -> list(validation_error()).
require(Errors, Condition, Route, Item, Expected) ->
    case Condition of
        true ->
            Errors;

        false ->
            [error(Route, Item, Expected) | Errors]
    end.

-file("src/proute/validate/pages.gleam", 93).
-spec validate_shape(proute@discover:page_route(), module_shape()) -> {ok,
        page_module()} |
    {error, list(validation_error())}.
validate_shape(Route, Shape) ->
    Errors = begin
        _pipe = [],
        _pipe@1 = require(
            _pipe,
            erlang:element(2, Shape),
            Route,
            <<"Model"/utf8>>,
            <<"pub type Model"/utf8>>
        ),
        _pipe@2 = require(
            _pipe@1,
            erlang:element(3, Shape),
            Route,
            <<"Message"/utf8>>,
            <<"pub type Message"/utf8>>
        ),
        _pipe@3 = require_optional_arity(
            _pipe@2,
            erlang:element(4, Shape),
            expected_init_arity(Route),
            Route,
            <<"init"/utf8>>,
            expected_optional_init_shape(Route)
        ),
        _pipe@4 = require_arity(
            _pipe@3,
            erlang:element(5, Shape),
            expected_init_arity(Route),
            Route,
            <<"initial_model"/utf8>>,
            expected_initial_model_shape(Route)
        ),
        _pipe@5 = require_any_arity(
            _pipe@4,
            erlang:element(6, Shape),
            [2, 3],
            Route,
            <<"update"/utf8>>,
            <<"pub fn update(model, msg) -> #(Model, Effect(Message)) or pub fn update(page_shared_state, model, msg) -> #(Model, Effect(Message))"/utf8>>
        ),
        require_arity(
            _pipe@5,
            result_to_list(erlang:element(7, Shape)),
            1,
            Route,
            <<"view"/utf8>>,
            <<"pub fn view(model) -> Element(Message)"/utf8>>
        )
    end,
    case Errors of
        [] ->
            {ok,
                {page_module,
                    Route,
                    gleam@list:contains(
                        erlang:element(4, Shape),
                        expected_init_arity(Route)
                    ),
                    gleam@list:contains(erlang:element(6, Shape), 3)}};

        _ ->
            {error, lists:reverse(Errors)}
    end.

-file("src/proute/validate/pages.gleam", 76).
-spec validate_route(proute@discover:page_route()) -> {ok, page_module()} |
    {error, list(validation_error())}.
validate_route(Route) ->
    case simplifile:read(erlang:element(7, Route)) of
        {error, _} ->
            _pipe = [{validation_error,
                    erlang:element(7, Route),
                    <<"page file"/utf8>>,
                    <<"a readable Gleam source file"/utf8>>}],
            {error, _pipe};

        {ok, Source} ->
            validate_shape(Route, module_shape(Source))
    end.

-file("src/proute/validate/pages.gleam", 52).
-spec validate_routes(
    list(proute@discover:page_route()),
    list(page_module()),
    list(validation_error())
) -> {ok, list(page_module())} | {error, list(validation_error())}.
validate_routes(Routes, Modules, Errors) ->
    case Routes of
        [] ->
            case Errors of
                [] ->
                    {ok, lists:reverse(Modules)};

                _ ->
                    {error, lists:reverse(Errors)}
            end;

        [Route | Rest] ->
            case validate_route(Route) of
                {ok, Module_} ->
                    validate_routes(Rest, [Module_ | Modules], Errors);

                {error, Route_errors} ->
                    validate_routes(
                        Rest,
                        Modules,
                        lists:append(lists:reverse(Route_errors), Errors)
                    )
            end
    end.

-file("src/proute/validate/pages.gleam", 34).
-spec validate_mount(proute@discover:mount_routes()) -> {ok,
        list(page_module())} |
    {error, list(validation_error())}.
validate_mount(Mount_routes) ->
    {mount_routes, _, Routes} = Mount_routes,
    validate_routes(Routes, [], []).

-file("src/proute/validate/pages.gleam", 42).
-spec describe_error(validation_error()) -> binary().
describe_error(Error) ->
    <<<<<<<<<<<<"Invalid page module "/utf8,
                            (gleam@string:inspect(erlang:element(2, Error)))/binary>>/binary,
                        ": "/utf8>>/binary,
                    (erlang:element(3, Error))/binary>>/binary,
                ". Expected "/utf8>>/binary,
            (erlang:element(4, Error))/binary>>/binary,
        "."/utf8>>.