-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([generate_with_extra_params/7, generate/6, generate_atoms_erl/4]). -export_type([extra_param/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( " Server-side dispatch generator.\n" "\n" " Produces dispatch source 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" "\n" " Each handler call is wrapped in `trace.try_call` so a panicked handler\n" " produces a structured InternalError response (with trace ID for log\n" " correlation) instead of crashing the caller's process.\n" ). -type extra_param() :: {extra_param, binary(), binary(), binary()}. -file("src/libero/codegen_dispatch.gleam", 321). -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", 258). -spec emit_case_arm( libero@scanner:handler_endpoint(), gleam@option:option(binary()), binary() ) -> binary(). emit_case_arm(E, Wire_module, Extra_args) -> Variant_name = libero@codegen:to_pascal_case( <<"server_"/utf8, (erlang:element(3, E))/binary>> ), Alias = handler_alias(erlang:element(2, E)), Param_destructure = libero@codegen:variant_pattern( Variant_name, erlang:element(6, E) ), Handler_args = case erlang:element(8, E) of {some, _} -> <<"wire.coerce(typed_msg), server_context"/utf8, Extra_args/binary>>; none -> Positional = gleam@list:map( erlang:element(6, E), fun(P) -> erlang:element(1, P) end ), gleam@string:join( lists:append( Positional, [<<"server_context"/utf8, Extra_args/binary>>] ), <<", "/utf8>> ) end, Raw_call = <<<<<<<<<<<>/binary, "server_"/utf8>>/binary, (erlang:element(3, E))/binary>>/binary, "("/utf8>>/binary, Handler_args/binary>>/binary, ")"/utf8>>, Ok_destructure = case erlang:element(7, E) of true -> <<"#(result, new_ctx)"/utf8>>; false -> <<"result"/utf8>> end, Ok_ctx = case erlang:element(7, E) of true -> <<"new_ctx"/utf8>>; false -> <<"server_context"/utf8>> end, Encode_line = case Wire_module of {some, _} -> <<<<" let result = wire_encode_response_"/utf8, (erlang:element(3, E))/binary>>/binary, "(result)\n"/utf8>>; none -> <<""/utf8>> end, <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" "/utf8, Param_destructure/binary>>/binary, " -> {\n"/utf8>>/binary, " case trace.try_call(fn() { "/utf8>>/binary, Raw_call/binary>>/binary, " }) {\n"/utf8>>/binary, " Ok("/utf8>>/binary, Ok_destructure/binary>>/binary, ") -> {\n"/utf8>>/binary, Encode_line/binary>>/binary, " #(wire.encode_response(request_id:, value:Ok(result)), "/utf8>>/binary, Ok_ctx/binary>>/binary, ")\n"/utf8>>/binary, " }\n"/utf8>>/binary, " Error(reason) -> {\n"/utf8>>/binary, " let trace_id = trace.new_trace_id()\n"/utf8>>/binary, " io.println_error(\"[libero] \" <> trace_id <> \" "/utf8>>/binary, (erlang:element(3, E))/binary>>/binary, ": \" <> reason)\n"/utf8>>/binary, " #(wire.encode_response(request_id:, value:Error(InternalError(trace_id:, message: \"Something went wrong\"))), server_context)\n"/utf8>>/binary, " }\n"/utf8>>/binary, " }\n"/utf8>>/binary, " }"/utf8>>. -file("src/libero/codegen_dispatch.gleam", 47). ?DOC( " Generate dispatch with additional pass-through parameters on handle()\n" " and every handler call. Each ExtraParam adds an import, a function\n" " parameter, and a labeled argument threaded to handlers.\n" ). -spec generate_with_extra_params( list(libero@scanner:handler_endpoint()), binary(), binary(), binary(), gleam@option:option(binary()), gleam@option:option(binary()), list(extra_param()) ) -> binary(). generate_with_extra_params( Endpoints, Context_module, Context_type_name, Wire_module_tag, Atoms_module, Wire_module, Extra_params ) -> Extra_import_lines = begin _pipe = Extra_params, _pipe@1 = gleam@list:filter( _pipe, fun(P) -> erlang:element(4, P) /= <<""/utf8>> end ), _pipe@2 = gleam@list:map( _pipe@1, fun(P@1) -> erlang:element(4, P@1) end ), gleam@list:unique(_pipe@2) end, Extra_handle_params = begin _pipe@3 = Extra_params, _pipe@4 = gleam@list:map( _pipe@3, fun(P@2) -> <<<<<<<<<<<<"\n "/utf8, (erlang:element(2, P@2))/binary>>/binary, " "/utf8>>/binary, (erlang:element(2, P@2))/binary>>/binary, ": "/utf8>>/binary, (erlang:element(3, P@2))/binary>>/binary, ","/utf8>> end ), gleam@string:join(_pipe@4, <<""/utf8>>) end, Extra_args = begin _pipe@5 = Extra_params, _pipe@6 = gleam@list:map( _pipe@5, fun(P@3) -> <<", "/utf8, (erlang:element(2, P@3))/binary>> end ), gleam@string:join(_pipe@6, <<""/utf8>>) end, Handler_modules = begin _pipe@7 = Endpoints, _pipe@8 = gleam@list:map(_pipe@7, fun(E) -> erlang:element(2, E) end), gleam@list:unique(_pipe@8) 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 ), Base_resolve = libero@codegen:build_alias_resolver(Endpoints), Resolve_alias = fun(Module_path) -> case gleam@list:contains(Handler_modules, Module_path) of true -> handler_alias(Module_path); false -> Base_resolve(Module_path) end end, Shared_type_imports = begin _pipe@9 = libero@codegen:collect_endpoint_type_modules(Endpoints, false), _pipe@10 = gleam@list:filter( _pipe@9, fun(Module_path@1) -> not gleam@list:contains(Handler_modules, Module_path@1) end ), gleam@list:map( _pipe@10, fun(Module_path@2) -> Alias@1 = Resolve_alias(Module_path@2), case Alias@1 =:= libero@field_type:last_segment(Module_path@2) of true -> <<"import "/utf8, Module_path@2/binary>>; false -> <<<<<<"import "/utf8, Module_path@2/binary>>/binary, " as "/utf8>>/binary, Alias@1/binary>> end end ) end, Dict_import = libero@codegen:import_if( Endpoints, fun libero@codegen:is_dict/1, <<"import gleam/dict.{type Dict}"/utf8>> ), Option_import = libero@codegen:import_if( Endpoints, fun libero@codegen:is_option/1, <<"import gleam/option.{type Option}"/utf8>> ), Client_msg_variants = libero@codegen:emit_client_msg_variants( Endpoints, Resolve_alias ), Known_tag_arms = begin _pipe@11 = Endpoints, _pipe@12 = gleam@list:map( _pipe@11, fun(E@1) -> <<<<<<<<<<" Ok(\"server_"/utf8, (erlang:element(3, E@1))/binary>>/binary, "\") ->\n"/utf8>>/binary, " dispatch_known(msg, request_id, server_context"/utf8>>/binary, Extra_args/binary>>/binary, ")"/utf8>> end ), gleam@string:join(_pipe@12, <<"\n"/utf8>>) end, Case_arms = gleam@list:map( Endpoints, fun(_capture) -> emit_case_arm(_capture, Wire_module, Extra_args) end ), Atoms_external = case Atoms_module of {some, Mod@1} -> <<<<"\n/// Pre-register all constructor atoms that may appear in client ETF /// payloads, so binary_to_term([safe]) can decode them. Called once /// on the first RPC call; subsequent calls are a no-op (persistent_term /// lookup). @external(erlang, \""/utf8, Mod@1/binary>>/binary, "\", \"ensure\") fn ensure_atoms() -> Nil "/utf8>>; none -> <<""/utf8>> end, Ensure_call = case Atoms_module of {some, _} -> <<" ensure_atoms()\n "/utf8>>; none -> <<""/utf8>> end, Inner_case = case Endpoints of [] -> <<<<<<<<<<" Ok(_) ->\n"/utf8, " #(wire.encode_response(request_id:, value:Error(UnknownFunction(\""/utf8>>/binary, Wire_module_tag/binary>>/binary, "\"))), server_context)\n"/utf8>>/binary, " Error(_) ->\n"/utf8>>/binary, " #(wire.encode_response(request_id:, value:Error(MalformedRequest)), server_context)"/utf8>>; _ -> <<<<<<<<<<<<<>/binary, " Ok(tag) ->\n"/utf8>>/binary, " #(wire.encode_response(request_id:, value:Error(UnknownFunction(\""/utf8>>/binary, Wire_module_tag/binary>>/binary, ".\" <> tag))), server_context)\n"/utf8>>/binary, " Error(_) ->\n"/utf8>>/binary, " #(wire.encode_response(request_id:, value:Error(MalformedRequest)), server_context)"/utf8>> end, Wire_externals = case {Wire_module, Endpoints} of {{some, Mod@2}, [_ | _]} -> Decode_ext = <<<<"\n@external(erlang, \""/utf8, Mod@2/binary>>/binary, "\", \"decode_client_msg\")\nfn wire_decode_client_msg(msg: a) -> b\n"/utf8>>, Response_exts = begin _pipe@13 = gleam@list:map( Endpoints, fun(E@2) -> <<<<<<<<<<<<"@external(erlang, \""/utf8, Mod@2/binary>>/binary, "\", \"encode_response_"/utf8>>/binary, (erlang:element(3, E@2))/binary>>/binary, "\")\nfn wire_encode_response_"/utf8>>/binary, (erlang:element(3, E@2))/binary>>/binary, "(result: a) -> b\n"/utf8>> end ), gleam@string:join(_pipe@13, <<"\n"/utf8>>) end, <<<>/binary, Response_exts/binary>>; {_, _} -> <<""/utf8>> end, Decode_msg_call = case Wire_module of {some, _} -> <<" let msg = wire_decode_client_msg(msg)\n"/utf8>>; none -> <<""/utf8>> end, Dispatch_known = case Endpoints of [] -> <<""/utf8>>; _ -> <<<<<<<<" fn dispatch_known(msg, request_id, server_context"/utf8, Extra_args/binary>>/binary, ") { case trace.try_call(fn() { let typed_msg: ClientMsg = wire.coerce(msg) case typed_msg { "/utf8>>/binary, (gleam@string:join(Case_arms, <<"\n"/utf8>>))/binary>>/binary, " } }) { Ok(response) -> response Error(reason) -> { let trace_id = trace.new_trace_id() io.println_error(\"[libero] \" <> trace_id <> \" malformed message: \" <> reason) #(wire.encode_response(request_id:, value:Error(MalformedRequest)), server_context) } } } "/utf8>> end, <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"//// Code generated by libero. DO NOT EDIT. import gleam/io import libero/error.{InternalError, MalformedRequest, UnknownFunction} import libero/trace import libero/etf/wire"/utf8, Dict_import/binary>>/binary, Option_import/binary>>/binary, " import "/utf8>>/binary, Context_module/binary>>/binary, ".{type "/utf8>>/binary, Context_type_name/binary>>/binary, "} "/utf8>>/binary, (gleam@string:join( Handler_imports, <<"\n"/utf8>> ))/binary>>/binary, " "/utf8>>/binary, (gleam@string:join( Shared_type_imports, <<"\n"/utf8>> ))/binary>>/binary, " "/utf8>>/binary, (case Extra_import_lines of [] -> <<""/utf8>>; Lines -> <<(gleam@string:join( Lines, <<"\n"/utf8>> ))/binary, "\n"/utf8>> end)/binary>>/binary, Atoms_external/binary>>/binary, Wire_externals/binary>>/binary, " pub type ClientMsg { "/utf8>>/binary, (gleam@string:join( Client_msg_variants, <<"\n"/utf8>> ))/binary>>/binary, " } pub fn handle( server_context server_context: "/utf8>>/binary, Context_type_name/binary>>/binary, ", data data: BitArray,"/utf8>>/binary, Extra_handle_params/binary>>/binary, " ) -> #(BitArray, "/utf8>>/binary, Context_type_name/binary>>/binary, ") { "/utf8>>/binary, Ensure_call/binary>>/binary, "case wire.decode_request(data) { Ok(#(\""/utf8>>/binary, Wire_module_tag/binary>>/binary, "\", request_id, msg)) -> { "/utf8>>/binary, Decode_msg_call/binary>>/binary, " case wire.variant_tag(msg) { "/utf8>>/binary, Inner_case/binary>>/binary, " } } Ok(#(name, request_id, _)) -> #(wire.encode_response(request_id:, value:Error(UnknownFunction(name))), server_context) Error(_) -> #(wire.encode_response(request_id: 0, value: Error(MalformedRequest)), server_context) } } "/utf8>>/binary, Dispatch_known/binary>>. -file("src/libero/codegen_dispatch.gleam", 25). ?DOC(" Generate dispatch.gleam source from scanned endpoints.\n"). -spec generate( list(libero@scanner:handler_endpoint()), binary(), binary(), binary(), gleam@option:option(binary()), gleam@option:option(binary()) ) -> binary(). generate( Endpoints, Context_module, Context_type_name, Wire_module_tag, Atoms_module, Wire_module ) -> generate_with_extra_params( Endpoints, Context_module, Context_type_name, Wire_module_tag, Atoms_module, Wire_module, [] ). -file("src/libero/codegen_dispatch.gleam", 334). ?DOC( " Generate an Erlang FFI file that pre-registers all constructor atoms\n" " discovered from handler endpoints, plus framework atoms used by\n" " libero's wire protocol. Calling `ensure/0` from this module creates\n" " the atoms in the BEAM atom table so that `binary_to_term([safe])`\n" " can decode client ETF payloads without rejecting unknown atoms.\n" "\n" " Under the wire-identity scheme there is no AtomMap: identity is\n" " baked into the per-type transformer functions in `_wire.erl`\n" " at codegen time, so the runtime carries no qualification table.\n" ). -spec generate_atoms_erl( list(libero@scanner:handler_endpoint()), list(libero@walker:discovered_type()), binary(), gleam@option:option(binary()) ) -> binary(). generate_atoms_erl(Endpoints, Discovered, Atoms_module, Wire_module) -> Framework_atoms = [<<"ok"/utf8>>, <<"error"/utf8>>, <<"some"/utf8>>, <<"none"/utf8>>, <<"nil"/utf8>>, <<"true"/utf8>>, <<"false"/utf8>>, <<"malformed_request"/utf8>>, <<"unknown_function"/utf8>>, <<"internal_error"/utf8>>, <<"decode_error"/utf8>>], Handler_atoms = gleam@list:flat_map( Endpoints, fun(E) -> [erlang:element(3, E), <<"server_"/utf8, (erlang:element(3, E))/binary>>] end ), Variant_atoms = gleam@list:flat_map( Discovered, fun(Dt) -> gleam@list:flat_map( erlang:element(5, Dt), fun(V) -> {_, Hash} = libero@wire_identity:wire_identity( erlang:element(2, V), erlang:element(3, V), erlang:element(7, V) ), [libero@walker:to_snake_case(erlang:element(3, V)), Hash] end ) end ), All_atoms = begin _pipe = lists:append([Framework_atoms, Handler_atoms, Variant_atoms]), _pipe@1 = gleam@list:unique(_pipe), gleam@list:sort(_pipe@1, fun gleam@string:compare/2) end, Atom_list = begin _pipe@2 = gleam@list:map( All_atoms, fun(Atom) -> <<<<" <<\""/utf8, Atom/binary>>/binary, "\">>"/utf8>> end ), gleam@string:join(_pipe@2, <<",\n"/utf8>>) end, <<<<<<<<<<<<"%% Code generated by libero. DO NOT EDIT. %% %% Pre-registers all atoms that may appear in client ETF payloads, %% so binary_to_term([safe]) can decode them without rejecting unknown %% atoms. Includes framework atoms, handler function names, bare %% constructor names, and the 10-char hex wire-identity hashes that %% the per-type transformer functions emit on the wire. %% %% ensure/0 uses persistent_term as a one-shot guard so the %% binary_to_atom calls only run once per VM lifetime. -module("/utf8, Atoms_module/binary>>/binary, "). -export([ensure/0]). ensure() -> case persistent_term:get({?MODULE, done}, false) of true -> nil; false -> do_ensure() end. do_ensure() -> lists:foreach(fun(B) -> binary_to_atom(B) end, [ "/utf8>>/binary, Atom_list/binary>>/binary, " ]), "/utf8>>/binary, (case Wire_module of {some, Mod} -> <<<<"persistent_term:put({libero, wire_module}, '"/utf8, Mod/binary>>/binary, "'),\n "/utf8>>; none -> <<""/utf8>> end)/binary>>/binary, "persistent_term:put({?MODULE, done}, true), nil. "/utf8>>.