src/rally@generator@http_handler.erl

-module(rally@generator@http_handler).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/rally/generator/http_handler.gleam").
-export([generate/7]).
-export_type([page_auth_info/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(
    " HTTP RPC handler codegen.\n"
    "\n"
    " Generates http_handler.gleam: handles POST /rpc requests for\n"
    " non-WebSocket clients. Decodes the request body through protocol_wire,\n"
    " dispatches to the RPC handler, and returns the response. When auth is\n"
    " configured, runs resolve/is_authenticated/authorize per request.\n"
).

-type page_auth_info() :: {page_auth_info,
        binary(),
        binary(),
        boolean(),
        boolean(),
        binary()}.

-file("src/rally/generator/http_handler.gleam", 86).
-spec last_segment(binary()) -> binary().
last_segment(Module_path) ->
    case begin
        _pipe = gleam@string:split(Module_path, <<"/"/utf8>>),
        gleam@list:last(_pipe)
    end of
        {ok, Seg} ->
            Seg;

        {error, nil} ->
            Module_path
    end.

-file("src/rally/generator/http_handler.gleam", 428).
-spec import_as(binary(), binary()) -> binary().
import_as(Module_path, Alias) ->
    case last_segment(Module_path) =:= Alias of
        true ->
            <<"import "/utf8, Module_path/binary>>;

        false ->
            <<<<<<"import "/utf8, Module_path/binary>>/binary, " as "/utf8>>/binary,
                Alias/binary>>
    end.

-file("src/rally/generator/http_handler.gleam", 51).
-spec generate_no_auth(binary(), binary(), binary()) -> binary().
generate_no_auth(_, Wire_import_module, _) ->
    <<<<<<"// Generated by Rally — do not edit.

import gleam/bytes_tree
import gleam/http/response.{type Response}
import mist.{type ResponseData}
"/utf8,
                (import_as(Wire_import_module, <<"wire"/utf8>>))/binary>>/binary,
            "\n"/utf8>>/binary,
        (begin
            _pipe = <<"import rally_runtime/effect
import server_context.{type ServerContext}

pub fn handle(
  body body: BitArray,
  server_context server_context: ServerContext,
  session_id session_id: String,
) -> Response(ResponseData) {
  let Nil = effect.put_ws_session(session_id)
  case wire.decode_rpc_envelope(body) {
    Ok(envelope) -> {
      let #(result, _new_ctx) = wire.dispatch_rpc(envelope, server_context)
      response.new(200)
      |> response.set_header(\"content-type\", wire.rpc_content_type())
      |> response.set_body(mist.Bytes(wire.rpc_result_body(result)))
    }
    Error(Nil) ->
      response.new(400)
      |> response.set_body(mist.Bytes(bytes_tree.from_string(\"Bad request\")))
  }
}
"/utf8>>,
            gleam@string:trim(_pipe)
        end)/binary>>.

-file("src/rally/generator/http_handler.gleam", 417).
-spec module_to_alias(binary()) -> binary().
module_to_alias(Module_path) ->
    gleam@string:replace(Module_path, <<"/"/utf8>>, <<"_"/utf8>>).

-file("src/rally/generator/http_handler.gleam", 276).
-spec generate_check_page_authorize(list(page_auth_info()), binary()) -> binary().
generate_check_page_authorize(Map, Auth_ref) ->
    With_auth = begin
        _pipe = Map,
        _pipe@1 = gleam@list:filter(
            _pipe,
            fun(Info) -> erlang:element(5, Info) end
        ),
        _pipe@2 = gleam@list:map(
            _pipe@1,
            fun(Info@1) ->
                {erlang:element(2, Info@1), erlang:element(3, Info@1)}
            end
        ),
        gleam@list:unique(_pipe@2)
    end,
    case With_auth of
        [] ->
            <<""/utf8>>;

        _ ->
            Arms = begin
                _pipe@3 = gleam@list:map(
                    With_auth,
                    fun(Info@2) ->
                        {Page, Module_path} = Info@2,
                        <<<<<<<<"    \""/utf8, Page/binary>>/binary,
                                    "\" -> "/utf8>>/binary,
                                (module_to_alias(Module_path))/binary>>/binary,
                            ".authorize(server_context, identity)"/utf8>>
                    end
                ),
                _pipe@4 = gleam@string:join(_pipe@3, <<"\n"/utf8>>),
                (fun(S) -> <<S/binary, "\n    _ -> False"/utf8>> end)(_pipe@4)
            end,
            <<<<<<<<"
fn check_page_authorize(page: String, server_context: ServerContext, identity: "/utf8,
                            Auth_ref/binary>>/binary,
                        ".Identity) -> Bool {
  case page {
"/utf8>>/binary,
                    Arms/binary>>/binary,
                "
  }
}"/utf8>>
    end.

-file("src/rally/generator/http_handler.gleam", 421).
-spec bool_str(boolean()) -> binary().
bool_str(B) ->
    case B of
        true ->
            <<"True"/utf8>>;

        false ->
            <<"False"/utf8>>
    end.

-file("src/rally/generator/http_handler.gleam", 253).
-spec generate_handler_page_info(list(page_auth_info())) -> binary().
generate_handler_page_info(Map) ->
    Arms = begin
        _pipe = gleam@list:map(
            Map,
            fun(Info) ->
                <<<<<<<<<<<<<<<<"    \""/utf8,
                                                (erlang:element(6, Info))/binary>>/binary,
                                            "\" -> Ok(PageAuthInfo(page: \""/utf8>>/binary,
                                        (erlang:element(2, Info))/binary>>/binary,
                                    "\", required: "/utf8>>/binary,
                                (bool_str(erlang:element(4, Info)))/binary>>/binary,
                            ", has_authorize: "/utf8>>/binary,
                        (bool_str(erlang:element(5, Info)))/binary>>/binary,
                    "))"/utf8>>
            end
        ),
        _pipe@1 = gleam@string:join(_pipe, <<"\n"/utf8>>),
        (fun(S) -> <<S/binary, "\n    _ -> Error(Nil)"/utf8>> end)(_pipe@1)
    end,
    <<<<"fn handler_page_info(variant: String) -> Result(PageAuthInfo, Nil) {
  case variant {
"/utf8,
            Arms/binary>>/binary,
        "
  }
}"/utf8>>.

-file("src/rally/generator/http_handler.gleam", 310).
-spec generate_auth_flow(list(page_auth_info()), binary(), binary()) -> binary().
generate_auth_flow(Map, Auth_ref, From_session_ref) ->
    Decode_and_lookup = <<"      case wire.decode_rpc_envelope(body) {
        Ok(envelope) ->
          case handler_page_info(wire.rpc_identity(envelope)) {
                Error(Nil) -> {
                  let result =
                    wire.error_result(wire.rpc_request_id(envelope), \"Unknown RPC\")
                  response.new(400)
                  |> response.set_header(\"content-type\", wire.rpc_content_type())
                  |> response.set_body(mist.Bytes(wire.rpc_result_body(result)))
                }
                Ok(info) -> {
                  let page = info.page
                  let required = info.required
                  let has_authorize = info.has_authorize"/utf8>>,
    Is_auth_check = <<<<"
                  case required && !"/utf8,
            Auth_ref/binary>>/binary,
        ".is_authenticated(identity) {
                    True -> {
                      let result =
                        wire.auth_error_result(wire.rpc_request_id(envelope), \"Authentication required\")
                      response.new(401)
                      |> response.set_header(\"content-type\", wire.rpc_content_type())
                      |> response.set_body(mist.Bytes(wire.rpc_result_body(result)))
                    }
                    False -> {"/utf8>>,
    From_session = <<<<"
                      let #(_, server_context) = "/utf8,
            From_session_ref/binary>>/binary,
        ".from_session(server_context: server_context, session_id: session_id, hostname: hostname, identity: identity)"/utf8>>,
    Authorize_check = <<"
                      case has_authorize && !check_page_authorize(page, server_context, identity) {
                        True -> {
                          let result =
                            wire.auth_error_result(wire.rpc_request_id(envelope), \"Forbidden\")
                          response.new(403)
                          |> response.set_header(\"content-type\", wire.rpc_content_type())
                          |> response.set_body(mist.Bytes(wire.rpc_result_body(result)))
                        }
                        False -> {"/utf8>>,
    Dispatch = <<"
                        let #(result, _new_ctx) = wire.dispatch_rpc(envelope, server_context, identity)
                        response.new(200)
                        |> response.set_header(\"content-type\", wire.rpc_content_type())
                        |> response.set_body(mist.Bytes(wire.rpc_result_body(result)))"/utf8>>,
    Close_page_info = <<"
                }
              }
        Error(Nil) ->
          response.new(400)
          |> response.set_body(mist.Bytes(bytes_tree.from_string(\"Bad request\")))
      }"/utf8>>,
    Has_any_required = gleam@list:any(
        Map,
        fun(Info) -> erlang:element(4, Info) end
    ),
    Has_any_authorize = gleam@list:any(
        Map,
        fun(Info@1) -> erlang:element(5, Info@1) end
    ),
    Auth_open = case Has_any_required of
        true ->
            Is_auth_check;

        false ->
            <<"
                  {"/utf8>>
    end,
    Auth_close = case Has_any_required of
        true ->
            <<"
                    }
                  }"/utf8>>;

        false ->
            <<"
                  }"/utf8>>
    end,
    Authorize_open = case Has_any_authorize of
        true ->
            Authorize_check;

        false ->
            <<"
                      {"/utf8>>
    end,
    Authorize_close = case Has_any_authorize of
        true ->
            <<"
                      }
                    }"/utf8>>;

        false ->
            <<"
                      }"/utf8>>
    end,
    <<<<<<<<<<<<<<Decode_and_lookup/binary, Auth_open/binary>>/binary,
                            From_session/binary>>/binary,
                        Authorize_open/binary>>/binary,
                    Dispatch/binary>>/binary,
                Authorize_close/binary>>/binary,
            Auth_close/binary>>/binary,
        Close_page_info/binary>>.

-file("src/rally/generator/http_handler.gleam", 237).
-spec generate_authorize_imports(list(page_auth_info())) -> binary().
generate_authorize_imports(Map) ->
    _pipe = Map,
    _pipe@1 = gleam@list:filter(_pipe, fun(Info) -> erlang:element(5, Info) end),
    _pipe@2 = gleam@list:map(
        _pipe@1,
        fun(Info@1) ->
            import_as(
                erlang:element(3, Info@1),
                module_to_alias(erlang:element(3, Info@1))
            )
        end
    ),
    _pipe@3 = gleam@list:unique(_pipe@2),
    _pipe@4 = gleam@list:sort(_pipe@3, fun gleam@string:compare/2),
    (fun(Imports) -> case Imports of
            [] ->
                <<""/utf8>>;

            _ ->
                <<(gleam@string:join(Imports, <<"\n"/utf8>>))/binary,
                    "\n"/utf8>>
        end end)(_pipe@4).

-file("src/rally/generator/http_handler.gleam", 225).
-spec to_pascal_case(binary()) -> binary().
to_pascal_case(Name) ->
    _pipe = Name,
    _pipe@1 = gleam@string:split(_pipe, <<"_"/utf8>>),
    _pipe@2 = gleam@list:map(
        _pipe@1,
        fun(Word) -> case gleam_stdlib:string_pop_grapheme(Word) of
                {ok, {First, Rest}} ->
                    <<(string:uppercase(First))/binary, Rest/binary>>;

                {error, nil} ->
                    Word
            end end
    ),
    gleam@string:join(_pipe@2, <<""/utf8>>).

-file("src/rally/generator/http_handler.gleam", 214).
-spec endpoint_json_tag(libero@scanner:handler_endpoint()) -> binary().
endpoint_json_tag(Endpoint) ->
    {Module_path@1, Type_name@1} = case erlang:element(8, Endpoint) of
        {some, {Module_path, Type_name}} ->
            {Module_path, Type_name};

        none ->
            {erlang:element(2, Endpoint),
                to_pascal_case(
                    <<"server_"/utf8, (erlang:element(3, Endpoint))/binary>>
                )}
    end,
    <<<<Module_path@1/binary, "."/utf8>>/binary, Type_name@1/binary>>.

-file("src/rally/generator/http_handler.gleam", 192).
-spec endpoint_wire_tags(libero@scanner:handler_endpoint(), binary()) -> list(binary()).
endpoint_wire_tags(Endpoint, Protocol) ->
    case Protocol of
        <<"json"/utf8>> ->
            [endpoint_json_tag(Endpoint)];

        _ ->
            Function_tag = <<"server_"/utf8,
                (erlang:element(3, Endpoint))/binary>>,
            Hash_tags = case erlang:element(8, Endpoint) of
                {some, {Module_path, Type_name}} ->
                    Fields = gleam@list:map(
                        erlang:element(6, Endpoint),
                        fun(Param) -> erlang:element(2, Param) end
                    ),
                    {_, Hash} = libero@wire_identity:wire_identity(
                        Module_path,
                        Type_name,
                        Fields
                    ),
                    [Hash];

                none ->
                    []
            end,
            lists:append([Function_tag], Hash_tags)
    end.

-file("src/rally/generator/http_handler.gleam", 157).
-spec build_page_auth_map(
    list(libero@scanner:handler_endpoint()),
    list({rally@types:scanned_route(), rally@types:page_contract()}),
    binary()
) -> list(page_auth_info()).
build_page_auth_map(Endpoints, Contracts, Protocol) ->
    gleam@list:flat_map(
        Endpoints,
        fun(Endpoint) ->
            case gleam@list:find_map(
                Contracts,
                fun(Pair) ->
                    {Route, Contract} = Pair,
                    case erlang:element(5, Route) =:= erlang:element(
                        2,
                        Endpoint
                    ) of
                        true ->
                            {ok, {Route, Contract}};

                        false ->
                            {error, nil}
                    end
                end
            ) of
                {ok, Page_contract} ->
                    {Route@1, Contract@1} = Page_contract,
                    _pipe = endpoint_wire_tags(Endpoint, Protocol),
                    gleam@list:map(
                        _pipe,
                        fun(Wire_tag) ->
                            {page_auth_info,
                                erlang:element(3, Route@1),
                                erlang:element(2, Endpoint),
                                erlang:element(15, Contract@1),
                                erlang:element(16, Contract@1),
                                Wire_tag}
                        end
                    );

                {error, nil} ->
                    erlang:error(#{gleam_error => panic,
                            message => <<"rally codegen: handler has no matching page contract"/utf8>>,
                            file => <<?FILEPATH/utf8>>,
                            module => <<"rally/generator/http_handler"/utf8>>,
                            function => <<"build_page_auth_map"/utf8>>,
                            line => 187})
            end
        end
    ).

-file("src/rally/generator/http_handler.gleam", 93).
-spec generate_with_auth(
    binary(),
    binary(),
    binary(),
    list(libero@scanner:handler_endpoint()),
    list({rally@types:scanned_route(), rally@types:page_contract()}),
    binary(),
    binary()
) -> binary().
generate_with_auth(
    _,
    Auth_module,
    From_session_module,
    Endpoints,
    Contracts,
    Wire_import_module,
    Protocol
) ->
    Auth_ref = last_segment(Auth_module),
    From_session_ref = last_segment(From_session_module),
    Page_auth_map = build_page_auth_map(Endpoints, Contracts, Protocol),
    Auth_import = import_as(Auth_module, Auth_ref),
    From_session_import = case From_session_ref =:= <<"server_context"/utf8>> of
        true ->
            <<""/utf8>>;

        false ->
            import_as(From_session_module, From_session_ref)
    end,
    Authorize_imports = generate_authorize_imports(Page_auth_map),
    Auth_checks = generate_auth_flow(Page_auth_map, Auth_ref, From_session_ref),
    <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"// Generated by Rally — do not edit.

import gleam/bytes_tree
import gleam/http/response.{type Response}
import mist.{type ResponseData}
import rally_runtime/effect
import server_context.{type ServerContext}
"/utf8,
                                                                    Auth_import/binary>>/binary,
                                                                "
"/utf8>>/binary,
                                                            (import_as(
                                                                Wire_import_module,
                                                                <<"wire"/utf8>>
                                                            ))/binary>>/binary,
                                                        "
"/utf8>>/binary,
                                                    From_session_import/binary>>/binary,
                                                "
"/utf8>>/binary,
                                            Authorize_imports/binary>>/binary,
                                        "

type PageAuthInfo {
  PageAuthInfo(page: String, required: Bool, has_authorize: Bool)
}

pub fn handle(
  body body: BitArray,
  server_context server_context: ServerContext,
  session_id session_id: String,
  hostname hostname: String,
) -> Response(ResponseData) {
  let Nil = effect.put_ws_session(session_id)
  case "/utf8>>/binary,
                                    Auth_ref/binary>>/binary,
                                ".resolve(server_context, session_id) {
    Error(Nil) ->
      response.new(500)
      |> response.set_body(mist.Bytes(bytes_tree.from_string(\"Auth service unavailable\")))
    Ok(identity) ->
"/utf8>>/binary,
                            Auth_checks/binary>>/binary,
                        "
  }
}
"/utf8>>/binary,
                    (generate_handler_page_info(Page_auth_map))/binary>>/binary,
                "
"/utf8>>/binary,
            (generate_check_page_authorize(Page_auth_map, Auth_ref))/binary>>/binary,
        (begin
            _pipe = <<"
"/utf8>>,
            gleam@string:trim(_pipe)
        end)/binary>>.

-file("src/rally/generator/http_handler.gleam", 27).
-spec generate(
    list(libero@scanner:handler_endpoint()),
    binary(),
    gleam@option:option(rally@types:auth_config()),
    list({rally@types:scanned_route(), rally@types:page_contract()}),
    binary(),
    binary(),
    binary()
) -> binary().
generate(
    Endpoints,
    Rpc_dispatch_module,
    Auth_config,
    Contracts,
    From_session_module,
    Wire_import_module,
    Protocol
) ->
    case Auth_config of
        {some, {auth_config, Auth_module}} ->
            generate_with_auth(
                Rpc_dispatch_module,
                Auth_module,
                From_session_module,
                Endpoints,
                Contracts,
                Wire_import_module,
                Protocol
            );

        none ->
            generate_no_auth(Rpc_dispatch_module, Wire_import_module, Protocol)
    end.