Skip to main content

src/proute@config.erl

-module(proute@config).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/proute/config.gleam").
-export([default_page_shared_state_type/1, parse/1, describe_error/1]).
-export_type([config/0, mount/0, raw_config/0, raw_mount/0, config_error/0]).

-type config() :: {config, binary(), binary(), list(mount())}.

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

-type raw_config() :: {raw_config, binary(), binary(), list(raw_mount())}.

-type raw_mount() :: {raw_mount,
        binary(),
        {ok, binary()} | {error, nil},
        {ok, binary()} | {error, nil},
        {ok, binary()} | {error, nil},
        {ok, binary()} | {error, nil}}.

-type config_error() :: {missing_config, binary()} |
    {malformed_config, binary()} |
    missing_proute_table |
    missing_mounts |
    {missing_mount_name, integer()} |
    {invalid_mount_name, binary()} |
    {duplicate_mount_name, binary()} |
    {invalid_constructor_prefix, binary(), binary()} |
    {invalid_route_root, binary(), binary()} |
    {invalid_page_shared_state_type, binary(), binary()}.

-file("src/proute/config.gleam", 352).
-spec join_path(list(binary())) -> binary().
join_path(Parts) ->
    _pipe = Parts,
    _pipe@1 = gleam@list:filter(_pipe, fun(Part) -> Part /= <<""/utf8>> end),
    gleam@string:join(_pipe@1, <<"/"/utf8>>).

-file("src/proute/config.gleam", 338).
-spec validate_constructor_prefix(binary(), binary()) -> {ok, nil} |
    {error, config_error()}.
validate_constructor_prefix(Mount_name, Constructor_prefix) ->
    case Constructor_prefix of
        <<""/utf8>> ->
            {ok, nil};

        _ ->
            case proute@names:is_valid_type_name(
                <<Constructor_prefix/binary, "Home"/utf8>>
            ) of
                true ->
                    {ok, nil};

                false ->
                    {error,
                        {invalid_constructor_prefix,
                            Mount_name,
                            Constructor_prefix}}
            end
    end.

-file("src/proute/config.gleam", 334).
-spec default_page_shared_state_type(binary()) -> binary().
default_page_shared_state_type(Name) ->
    <<<<<<Name/binary, "/page_shared_state."/utf8>>/binary,
            (proute@names:pascal_case(Name))/binary>>/binary,
        "PageSharedState"/utf8>>.

-file("src/proute/config.gleam", 327).
-spec default_constructor_prefix(binary(), binary()) -> binary().
default_constructor_prefix(Name, Route_root) ->
    case Route_root of
        <<"/"/utf8>> ->
            <<""/utf8>>;

        _ ->
            proute@names:pascal_case(Name)
    end.

-file("src/proute/config.gleam", 315).
-spec is_valid_route_root(binary()) -> boolean().
is_valid_route_root(Route_root) ->
    (Route_root =:= <<"/"/utf8>>) orelse (((((gleam_stdlib:string_starts_with(
        Route_root,
        <<"/"/utf8>>
    )
    andalso not gleam_stdlib:string_ends_with(Route_root, <<"/"/utf8>>))
    andalso not gleam_stdlib:contains_string(Route_root, <<"//"/utf8>>))
    andalso not gleam_stdlib:contains_string(Route_root, <<"?"/utf8>>))
    andalso not gleam_stdlib:contains_string(Route_root, <<"#"/utf8>>))
    andalso not gleam_stdlib:contains_string(Route_root, <<" "/utf8>>)).

-file("src/proute/config.gleam", 303).
-spec normalize_route_root(binary()) -> binary().
normalize_route_root(Route_root) ->
    Pieces = begin
        _pipe = Route_root,
        _pipe@1 = gleam@string:split(_pipe, <<"/"/utf8>>),
        gleam@list:filter(_pipe@1, fun(Piece) -> Piece /= <<""/utf8>> end)
    end,
    case Pieces of
        [] ->
            <<"/"/utf8>>;

        _ ->
            <<"/"/utf8, (gleam@string:join(Pieces, <<"/"/utf8>>))/binary>>
    end.

-file("src/proute/config.gleam", 293).
-spec resolve_route_root(raw_mount()) -> {ok, binary()} |
    {error, config_error()}.
resolve_route_root(Mount) ->
    Root = gleam@result:unwrap(
        erlang:element(4, Mount),
        <<"/"/utf8, (erlang:element(2, Mount))/binary>>
    ),
    Normalized = normalize_route_root(Root),
    case is_valid_route_root(Normalized) of
        true ->
            {ok, Normalized};

        false ->
            {error, {invalid_route_root, erlang:element(2, Mount), Root}}
    end.

-file("src/proute/config.gleam", 225).
-spec resolve_mount(raw_mount(), binary(), binary()) -> {ok, mount()} |
    {error, config_error()}.
resolve_mount(Mount, Pages_root, Output_root) ->
    gleam@result:'try'(
        resolve_route_root(Mount),
        fun(Route_root) ->
            Pages = gleam@result:unwrap(
                erlang:element(3, Mount),
                join_path(
                    [Pages_root, erlang:element(2, Mount), <<"pages"/utf8>>]
                )
            ),
            Output_dir = join_path([Output_root, erlang:element(2, Mount)]),
            Constructor_prefix = gleam@result:unwrap(
                erlang:element(5, Mount),
                default_constructor_prefix(erlang:element(2, Mount), Route_root)
            ),
            Page_shared_state_type = gleam@result:unwrap(
                erlang:element(6, Mount),
                default_page_shared_state_type(erlang:element(2, Mount))
            ),
            gleam@result:'try'(
                validate_constructor_prefix(
                    erlang:element(2, Mount),
                    Constructor_prefix
                ),
                fun(_) ->
                    gleam@result:'try'(
                        begin
                            _pipe = proute@names:parse_type_reference(
                                Page_shared_state_type
                            ),
                            gleam@result:map_error(
                                _pipe,
                                fun(_) ->
                                    {invalid_page_shared_state_type,
                                        erlang:element(2, Mount),
                                        Page_shared_state_type}
                                end
                            )
                        end,
                        fun(_) ->
                            {ok,
                                {mount,
                                    erlang:element(2, Mount),
                                    Pages,
                                    Route_root,
                                    Output_dir,
                                    join_path(
                                        [Output_dir, <<"routes.gleam"/utf8>>]
                                    ),
                                    join_path(
                                        [Output_dir, <<"pages.gleam"/utf8>>]
                                    ),
                                    join_path(
                                        [Output_dir,
                                            <<"page_input.gleam"/utf8>>]
                                    ),
                                    Constructor_prefix,
                                    Page_shared_state_type}}
                        end
                    )
                end
            )
        end
    ).

-file("src/proute/config.gleam", 289).
-spec is_valid_mount_name(binary()) -> boolean().
is_valid_mount_name(Name) ->
    proute@names:is_valid_module_path(Name).

-file("src/proute/config.gleam", 269).
-spec validate_mount_names(list(raw_mount()), gleam@dict:dict(binary(), nil)) -> {ok,
        nil} |
    {error, config_error()}.
validate_mount_names(Mounts, Seen) ->
    case Mounts of
        [] ->
            {ok, nil};

        [Mount | Rest] ->
            case is_valid_mount_name(erlang:element(2, Mount)) of
                false ->
                    {error, {invalid_mount_name, erlang:element(2, Mount)}};

                true ->
                    case gleam@dict:has_key(Seen, erlang:element(2, Mount)) of
                        true ->
                            {error,
                                {duplicate_mount_name, erlang:element(2, Mount)}};

                        false ->
                            validate_mount_names(
                                Rest,
                                gleam@dict:insert(
                                    Seen,
                                    erlang:element(2, Mount),
                                    nil
                                )
                            )
                    end
            end
    end.

-file("src/proute/config.gleam", 211).
-spec resolve(raw_config()) -> {ok, config()} | {error, config_error()}.
resolve(Raw) ->
    gleam@result:'try'(
        validate_mount_names(erlang:element(4, Raw), maps:new()),
        fun(_) -> _pipe = erlang:element(4, Raw),
            _pipe@1 = gleam@list:try_map(
                _pipe,
                fun(_capture) ->
                    resolve_mount(
                        _capture,
                        erlang:element(2, Raw),
                        erlang:element(3, Raw)
                    )
                end
            ),
            gleam@result:map(
                _pipe@1,
                fun(Mounts) ->
                    {config,
                        erlang:element(2, Raw),
                        erlang:element(3, Raw),
                        Mounts}
                end
            ) end
    ).

-file("src/proute/config.gleam", 415).
-spec describe_tom_error(tom:get_error()) -> binary().
describe_tom_error(Error) ->
    case Error of
        {not_found, Path} ->
            <<"missing "/utf8, (gleam@string:join(Path, <<"."/utf8>>))/binary>>;

        {wrong_type, _, Expected, Got} ->
            <<<<<<"expected "/utf8, Expected/binary>>/binary, ", got "/utf8>>/binary,
                Got/binary>>
    end.

-file("src/proute/config.gleam", 388).
-spec optional_string_result(
    gleam@dict:dict(binary(), tom:toml()),
    binary(),
    list(binary())
) -> {ok, {ok, binary()} | {error, nil}} | {error, config_error()}.
optional_string_result(Table, Field, Path) ->
    case tom:get_string(Table, [Field]) of
        {ok, Value} ->
            {ok, {ok, Value}};

        {error, {not_found, _}} ->
            {ok, {error, nil}};

        {error, Error} ->
            {error,
                {malformed_config,
                    <<<<<<"Invalid "/utf8,
                                (gleam@string:join(Path, <<"."/utf8>>))/binary>>/binary,
                            ": "/utf8>>/binary,
                        (describe_tom_error(Error))/binary>>}}
    end.

-file("src/proute/config.gleam", 358).
-spec optional_string(
    gleam@dict:dict(binary(), tom:toml()),
    binary(),
    list(binary()),
    binary()
) -> {ok, binary()} | {error, config_error()}.
optional_string(Table, Field, Path, Default) ->
    case optional_string_result(Table, Field, Path) of
        {ok, {ok, Value}} ->
            {ok, Value};

        {ok, {error, _}} ->
            {ok, Default};

        {error, Error} ->
            {error, Error}
    end.

-file("src/proute/config.gleam", 371).
-spec required_mount_name(gleam@dict:dict(binary(), tom:toml()), integer()) -> {ok,
        binary()} |
    {error, config_error()}.
required_mount_name(Table, Index) ->
    case tom:get_string(Table, [<<"name"/utf8>>]) of
        {ok, Value} ->
            {ok, Value};

        {error, {not_found, _}} ->
            {error, {missing_mount_name, Index}};

        {error, Error} ->
            {error,
                {malformed_config,
                    <<<<<<"Invalid proute.mounts."/utf8,
                                (erlang:integer_to_binary(Index))/binary>>/binary,
                            ".name: "/utf8>>/binary,
                        (describe_tom_error(Error))/binary>>}}
    end.

-file("src/proute/config.gleam", 155).
-spec read_mounts_loop(
    list(gleam@dict:dict(binary(), tom:toml())),
    integer(),
    list(raw_mount())
) -> {ok, list(raw_mount())} | {error, config_error()}.
read_mounts_loop(Mounts, Index, Read) ->
    case Mounts of
        [] ->
            {ok, lists:reverse(Read)};

        [Mount | Rest] ->
            gleam@result:'try'(
                required_mount_name(Mount, Index),
                fun(Name) ->
                    gleam@result:'try'(
                        optional_string_result(
                            Mount,
                            <<"pages"/utf8>>,
                            [<<"proute"/utf8>>,
                                <<"mounts"/utf8>>,
                                erlang:integer_to_binary(Index),
                                <<"pages"/utf8>>]
                        ),
                        fun(Pages) ->
                            gleam@result:'try'(
                                optional_string_result(
                                    Mount,
                                    <<"route_root"/utf8>>,
                                    [<<"proute"/utf8>>,
                                        <<"mounts"/utf8>>,
                                        erlang:integer_to_binary(Index),
                                        <<"route_root"/utf8>>]
                                ),
                                fun(Route_root) ->
                                    gleam@result:'try'(
                                        optional_string_result(
                                            Mount,
                                            <<"constructor_prefix"/utf8>>,
                                            [<<"proute"/utf8>>,
                                                <<"mounts"/utf8>>,
                                                erlang:integer_to_binary(Index),
                                                <<"constructor_prefix"/utf8>>]
                                        ),
                                        fun(Constructor_prefix) ->
                                            gleam@result:'try'(
                                                optional_string_result(
                                                    Mount,
                                                    <<"page_shared_state_type"/utf8>>,
                                                    [<<"proute"/utf8>>,
                                                        <<"mounts"/utf8>>,
                                                        erlang:integer_to_binary(
                                                            Index
                                                        ),
                                                        <<"page_shared_state_type"/utf8>>]
                                                ),
                                                fun(Page_shared_state_type) ->
                                                    read_mounts_loop(
                                                        Rest,
                                                        Index + 1,
                                                        [{raw_mount,
                                                                Name,
                                                                Pages,
                                                                Route_root,
                                                                Constructor_prefix,
                                                                Page_shared_state_type} |
                                                            Read]
                                                    )
                                                end
                                            )
                                        end
                                    )
                                end
                            )
                        end
                    )
                end
            )
    end.

-file("src/proute/config.gleam", 135).
-spec read_mounts(gleam@dict:dict(binary(), tom:toml())) -> {ok,
        list(raw_mount())} |
    {error, config_error()}.
read_mounts(Proute) ->
    case tom:get_array(Proute, [<<"mounts"/utf8>>]) of
        {error, {not_found, _}} ->
            {error, missing_mounts};

        {error, Error} ->
            {error,
                {malformed_config,
                    <<"Invalid proute.mounts: "/utf8,
                        (describe_tom_error(Error))/binary>>}};

        {ok, []} ->
            {error, missing_mounts};

        {ok, Mounts} ->
            case gleam@list:try_map(Mounts, fun tom:as_table/1) of
                {ok, Mounts@1} ->
                    _pipe = Mounts@1,
                    read_mounts_loop(_pipe, 1, []);

                {error, Error@1} ->
                    {error,
                        {malformed_config,
                            <<"Invalid proute.mounts: expected array of tables, "/utf8,
                                (describe_tom_error(Error@1))/binary>>}}
            end
    end.

-file("src/proute/config.gleam", 406).
-spec read_proute_table(gleam@dict:dict(binary(), tom:toml())) -> {ok,
        gleam@dict:dict(binary(), tom:toml())} |
    {error, config_error()}.
read_proute_table(Table) ->
    case tom:get_table(Table, [<<"proute"/utf8>>]) of
        {ok, Value} ->
            {ok, Value};

        {error, {not_found, _}} ->
            {error, missing_proute_table};

        {error, Error} ->
            {error,
                {malformed_config,
                    <<"Invalid proute: "/utf8,
                        (describe_tom_error(Error))/binary>>}}
    end.

-file("src/proute/config.gleam", 115).
-spec read_raw_config(gleam@dict:dict(binary(), tom:toml())) -> {ok,
        raw_config()} |
    {error, config_error()}.
read_raw_config(Document) ->
    gleam@result:'try'(
        read_proute_table(Document),
        fun(Proute) ->
            gleam@result:'try'(
                read_mounts(Proute),
                fun(Mounts) ->
                    gleam@result:'try'(
                        optional_string(
                            Proute,
                            <<"pages_root"/utf8>>,
                            [<<"proute"/utf8>>, <<"pages_root"/utf8>>],
                            <<"src"/utf8>>
                        ),
                        fun(Pages_root) ->
                            gleam@result:'try'(
                                optional_string(
                                    Proute,
                                    <<"output_root"/utf8>>,
                                    [<<"proute"/utf8>>, <<"output_root"/utf8>>],
                                    <<"src/generated/proute"/utf8>>
                                ),
                                fun(Output_root) ->
                                    {ok,
                                        {raw_config,
                                            Pages_root,
                                            Output_root,
                                            Mounts}}
                                end
                            )
                        end
                    )
                end
            )
        end
    ).

-file("src/proute/config.gleam", 58).
-spec parse(binary()) -> {ok, config()} | {error, config_error()}.
parse(Source) ->
    gleam@result:'try'(
        begin
            _pipe = tom:parse(Source),
            gleam@result:map_error(
                _pipe,
                fun(Error) ->
                    {malformed_config,
                        <<"Could not parse proute.toml: "/utf8,
                            (gleam@string:inspect(Error))/binary>>}
                end
            )
        end,
        fun(Document) ->
            gleam@result:'try'(
                read_raw_config(Document),
                fun(Raw) -> resolve(Raw) end
            )
        end
    ).

-file("src/proute/config.gleam", 70).
-spec describe_error(config_error()) -> binary().
describe_error(Error) ->
    case Error of
        {missing_config, Path} ->
            <<<<"Missing proute config: expected "/utf8, Path/binary>>/binary,
                " in the current directory."/utf8>>;

        {malformed_config, Message} ->
            Message;

        missing_proute_table ->
            <<"Missing [proute] config table in proute.toml."/utf8>>;

        missing_mounts ->
            <<"Missing [[proute.mounts]] entries in proute.toml."/utf8>>;

        {missing_mount_name, Index} ->
            <<<<"Missing required name for [[proute.mounts]] entry #"/utf8,
                    (erlang:integer_to_binary(Index))/binary>>/binary,
                "."/utf8>>;

        {invalid_mount_name, Name} ->
            <<<<"Invalid mount name "/utf8,
                    (gleam@string:inspect(Name))/binary>>/binary,
                ": use a valid Gleam module path segment."/utf8>>;

        {duplicate_mount_name, Name@1} ->
            <<<<"Duplicate mount name "/utf8,
                    (gleam@string:inspect(Name@1))/binary>>/binary,
                " in proute.toml."/utf8>>;

        {invalid_constructor_prefix, Name@2, Constructor_prefix} ->
            <<<<<<<<<<<<"Invalid constructor_prefix "/utf8,
                                    (gleam@string:inspect(Constructor_prefix))/binary>>/binary,
                                " for mount "/utf8>>/binary,
                            (gleam@string:inspect(Name@2))/binary>>/binary,
                        ": it must produce valid Gleam route constructors, such as "/utf8>>/binary,
                    (gleam@string:inspect(
                        <<Constructor_prefix/binary, "Home"/utf8>>
                    ))/binary>>/binary,
                "."/utf8>>;

        {invalid_route_root, Name@3, Route_root} ->
            <<<<<<<<"Invalid route_root "/utf8,
                            (gleam@string:inspect(Route_root))/binary>>/binary,
                        " for mount "/utf8>>/binary,
                    (gleam@string:inspect(Name@3))/binary>>/binary,
                ": route roots must normalize to / or an absolute path without empty segments."/utf8>>;

        {invalid_page_shared_state_type, Name@4, Value} ->
            <<<<<<<<<<<<"Invalid page_shared_state_type "/utf8,
                                    (gleam@string:inspect(Value))/binary>>/binary,
                                " for mount "/utf8>>/binary,
                            (gleam@string:inspect(Name@4))/binary>>/binary,
                        ": use a Gleam module path and type name, such as "/utf8>>/binary,
                    (gleam@string:inspect(
                        default_page_shared_state_type(Name@4)
                    ))/binary>>/binary,
                "."/utf8>>
    end.