-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.