-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 = <<<<<<<<<>/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 = <>, libero@codegen:ensure_parent_dir(Output), libero@codegen:write_file(Output, Content).