Current section
Files
Jump to
Current section
Files
src/libero@codegen_dispatch.erl
-module(libero@codegen_dispatch).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/libero/codegen_dispatch.gleam").
-export([write_endpoint_dispatch/5]).
-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(
" Server-side dispatch generator.\n"
"\n"
" Emits `dispatch.gleam` from a list of handler endpoints. The generated\n"
" module pattern-matches incoming wire envelopes onto handler function\n"
" calls and produces ETF-encoded responses.\n"
).
-file("src/libero/codegen_dispatch.gleam", 189).
?DOC(
" Convert a module path to a safe Gleam import alias.\n"
" e.g. \"server/store\" -> \"server_store_handler\"\n"
).
-spec handler_alias(binary()) -> binary().
handler_alias(Module_path) ->
<<(libero@codegen:module_to_underscored(Module_path))/binary,
"_handler"/utf8>>.
-file("src/libero/codegen_dispatch.gleam", 16).
?DOC(
" Generate a dispatch module from handler endpoint functions.\n"
" Each public function with a HandlerContext parameter becomes\n"
" an RPC endpoint.\n"
).
-spec write_endpoint_dispatch(
list(libero@scanner:handler_endpoint()),
binary(),
binary(),
binary(),
binary()
) -> {ok, nil} | {error, libero@gen_error:gen_error()}.
write_endpoint_dispatch(
Endpoints,
Server_generated,
Atoms_module,
Context_module,
Wire_module_tag
) ->
Handler_modules = begin
_pipe = Endpoints,
_pipe@1 = gleam@list:map(_pipe, fun(E) -> erlang:element(2, E) end),
gleam@list:unique(_pipe@1)
end,
Handler_imports = gleam@list:map(
Handler_modules,
fun(Mod) ->
Alias = handler_alias(Mod),
<<<<<<"import "/utf8, Mod/binary>>/binary, " as "/utf8>>/binary,
Alias/binary>>
end
),
Shared_type_imports = libero@codegen:collect_endpoint_type_imports(
Endpoints,
false
),
Dict_import = libero@codegen:import_if(
Endpoints,
fun libero@codegen:is_dict/1,
<<"import gleam/dict.{type Dict}"/utf8>>
),
Client_msg_variants = libero@codegen:emit_client_msg_variants(Endpoints),
Known_tag_pattern = begin
_pipe@2 = Endpoints,
_pipe@3 = gleam@list:map(
_pipe@2,
fun(E@1) ->
<<<<"Ok(\""/utf8, (erlang:element(3, E@1))/binary>>/binary,
"\")"/utf8>>
end
),
gleam@string:join(_pipe@3, <<"\n | "/utf8>>)
end,
Case_arms = gleam@list:map(
Endpoints,
fun(E@2) ->
Variant_name = libero@codegen:to_pascal_case(erlang:element(3, E@2)),
Alias@1 = handler_alias(erlang:element(2, E@2)),
Param_destructure = libero@codegen:variant_pattern(
Variant_name,
erlang:element(6, E@2)
),
Labeled = gleam@list:map(
erlang:element(6, E@2),
fun(P) -> <<(erlang:element(1, P))/binary, ":"/utf8>> end
),
Handler_args = gleam@string:join(
lists:append(Labeled, [<<"handler_ctx:"/utf8>>]),
<<", "/utf8>>
),
Raw_call = <<<<<<<<<<Alias@1/binary, "."/utf8>>/binary,
(erlang:element(3, E@2))/binary>>/binary,
"("/utf8>>/binary,
Handler_args/binary>>/binary,
")"/utf8>>,
Invocation = case erlang:element(7, E@2) of
true ->
Raw_call;
false ->
<<<<"#("/utf8, Raw_call/binary>>/binary,
", handler_ctx)"/utf8>>
end,
<<<<<<<<" "/utf8, Param_destructure/binary>>/binary,
" ->\n dispatch(handler_ctx, request_id, fn() { "/utf8>>/binary,
Invocation/binary>>/binary,
" })"/utf8>>
end
),
Content = <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"//// Code generated by libero. DO NOT EDIT.
////
//// Server-only: dispatch depends on `ensure_atoms` and `wire.decode_call`,
//// which have no JavaScript implementation. Compiling this module to JS
//// will fail at the unresolved-external boundary.
import gleam/option.{type Option, None, Some}"/utf8,
Dict_import/binary>>/binary,
"
import libero/error.{type PanicInfo, InternalError, MalformedRequest, UnknownFunction}
import libero/trace
import libero/wire
import "/utf8>>/binary,
Context_module/binary>>/binary,
".{type HandlerContext}
"/utf8>>/binary,
(gleam@string:join(
Handler_imports,
<<"\n"/utf8>>
))/binary>>/binary,
"
"/utf8>>/binary,
(gleam@string:join(
Shared_type_imports,
<<"\n"/utf8>>
))/binary>>/binary,
"
pub type ClientMsg {
"/utf8>>/binary,
(gleam@string:join(
Client_msg_variants,
<<"\n"/utf8>>
))/binary>>/binary,
"
}
@external(erlang, \""/utf8>>/binary,
Atoms_module/binary>>/binary,
"\", \"ensure\")
pub fn ensure_atoms() -> Nil
pub fn handle(
handler_ctx handler_ctx: HandlerContext,
data data: BitArray,
) -> #(BitArray, Option(PanicInfo), HandlerContext) {
case wire.decode_call(data) {
Ok(#(\""/utf8>>/binary,
Wire_module_tag/binary>>/binary,
"\", request_id, msg)) -> {
case wire.variant_tag(msg) {
"/utf8>>/binary,
Known_tag_pattern/binary>>/binary,
" -> {
let typed_msg: ClientMsg = wire.coerce(msg)
case typed_msg {
"/utf8>>/binary,
(gleam@string:join(Case_arms, <<"\n"/utf8>>))/binary>>/binary,
"
}
}
Ok(tag) ->
#(wire.tag_response(request_id:, data: wire.encode(Error(UnknownFunction(\""/utf8>>/binary,
Wire_module_tag/binary>>/binary,
".\" <> tag)))), None, handler_ctx)
Error(_) ->
#(wire.tag_response(request_id:, data: wire.encode(Error(MalformedRequest))), None, handler_ctx)
}
}
Ok(#(name, request_id, _)) ->
#(wire.tag_response(request_id:, data: wire.encode(Error(UnknownFunction(name)))), None, handler_ctx)
Error(_) ->
#(wire.tag_response(request_id: 0, data: wire.encode(Error(MalformedRequest))), None, handler_ctx)
}
}
fn dispatch(
handler_ctx handler_ctx: HandlerContext,
request_id request_id: Int,
call call: fn() -> #(a, HandlerContext),
) -> #(BitArray, Option(PanicInfo), HandlerContext) {
case trace.try_call(call) {
Ok(#(value, new_handler_ctx)) ->
safe_encode(fn() { wire.encode(Ok(value)) }, new_handler_ctx, request_id, \"dispatch_encode_ok\")
Error(reason) ->
internal_error_response(
request_id:,
fn_name: \"dispatch\",
message: \"Internal server error\",
reason:,
handler_ctx:,
)
}
}
fn safe_encode(
encoder: fn() -> BitArray,
handler_ctx: HandlerContext,
request_id: Int,
fn_name: String,
) -> #(BitArray, Option(PanicInfo), HandlerContext) {
case trace.try_call(encoder) {
Ok(bytes) -> #(wire.tag_response(request_id:, data: bytes), None, handler_ctx)
Error(reason) ->
internal_error_response(
request_id:,
fn_name:,
message: \"Response encoding failed\",
reason:,
handler_ctx:,
)
}
}
fn internal_error_response(
request_id request_id: Int,
fn_name fn_name: String,
message message: String,
reason reason: String,
handler_ctx handler_ctx: HandlerContext,
) -> #(BitArray, Option(PanicInfo), HandlerContext) {
let trace_id = trace.new_trace_id()
#(
wire.tag_response(request_id:, data: wire.encode(Error(InternalError(trace_id, message)))),
Some(error.PanicInfo(trace_id:, fn_name:, reason:)),
handler_ctx,
)
}
"/utf8>>,
Output = <<Server_generated/binary, "/dispatch.gleam"/utf8>>,
libero@codegen:ensure_parent_dir(Output),
libero@codegen:write_file(Output, Content).