Current section

Files

Jump to
libero src libero@wire.erl
Raw

src/libero@wire.erl

-module(libero@wire).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/libero/wire.gleam").
-export([encode/1, decode/1, decode_safe/1, decode_call/1, encode_call/2, tag_response/1, tag_push/2, coerce/1]).
-export_type([decode_error/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(
" ETF (Erlang Term Format) wire codec for Libero RPC.\n"
"\n"
" Encoding walks any Gleam value through `erlang:term_to_binary/1`,\n"
" which preserves the full Erlang type structure - atoms, tuples,\n"
" maps, lists - natively. Decoding uses `erlang:binary_to_term/1`\n"
" to reconstruct the original terms. No manual walk or rebuild is\n"
" needed because ETF is the BEAM's native serialization format.\n"
"\n"
" **Wire shape:**\n"
" - The call envelope is `{module_name_binary, msg_from_client_value}` - a\n"
" 2-tuple where the first element is a UTF-8 binary (Gleam String)\n"
" naming the shared module, and the second is the typed MsgFromClient\n"
" value serialized as a native ETF term.\n"
" - The response is the Gleam value directly (e.g. `Ok(value)` or\n"
" `Error(MalformedRequest)`), serialized as ETF.\n"
"\n"
" **Cross-target:** `encode` and `decode` work on both Erlang and\n"
" JavaScript targets. The Erlang path uses the BEAM's native\n"
" `term_to_binary` / `binary_to_term`. The JavaScript path uses\n"
" libero's own ETF encoder/decoder in `rpc_ffi.mjs`, which requires\n"
" that any custom-type constructors in the value have been registered\n"
" via `register_all()` at boot (libero's generator emits that\n"
" registration for every type reachable from the MsgFromClient/MsgFromServer type graph).\n"
).
-type decode_error() :: {decode_error, binary()}.
-file("src/libero/wire.gleam", 37).
?DOC(
" Encode any Gleam value to an ETF binary.\n"
"\n"
" Works on both Erlang and JavaScript targets. Used internally by\n"
" libero to serialize RPC responses, and also available for non-RPC\n"
" paths (e.g. passing server-rendered state into a Lustre SPA via\n"
" flags, in the Elm \"init flags\" style).\n"
).
-spec encode(any()) -> bitstring().
encode(Value) ->
libero_ffi:encode(Value).
-file("src/libero/wire.gleam", 71).
?DOC(
" Decode an ETF binary into an arbitrary Gleam value.\n"
"\n"
" Works on both Erlang and JavaScript targets. Use this for non-RPC\n"
" paths - for example, reading server-rendered state from Lustre\n"
" flags on client boot. For decoding incoming RPC call envelopes\n"
" specifically, use `decode_call` instead.\n"
"\n"
" Any custom types in the decoded value must be reachable from the\n"
" MsgFromClient/MsgFromServer type graph so their constructors are registered with the\n"
" JavaScript codec (via `register_all()` at boot). On Erlang this\n"
" is automatic because atoms are pre-registered by the generated\n"
" `rpc_atoms` module.\n"
"\n"
" **Warning: type safety is the caller's responsibility.** The return\n"
" type `a` is unwitnessed — the function returns whatever the ETF\n"
" binary deserializes to, cast to the caller's expected type. A\n"
" version skew between client and server will produce silent data\n"
" corruption, not a runtime error. This is an intentional tradeoff\n"
" for ergonomics in controlled deployments where both sides are\n"
" built from the same source.\n"
"\n"
" This is by design for v4 — adding a wire schema hash to detect\n"
" version skew is planned for v5 (see docs/request_ids.md).\n"
"\n"
" **Panics on malformed input.** In a typical libero deployment\n"
" both sides are controlled, so this is a sharp-edge check rather\n"
" than a user-facing error. For untrusted input, use `decode_safe`\n"
" which returns a `Result`.\n"
).
-spec decode(bitstring()) -> any().
decode(Data) ->
libero_ffi:decode(Data).
-file("src/libero/wire.gleam", 82).
?DOC(
" Decode an ETF binary into an arbitrary Gleam value, returning a\n"
" `Result` instead of panicking on malformed input.\n"
"\n"
" Use this for non-RPC paths where the input may be untrusted or\n"
" user-influenced - for example, reading server-rendered state from\n"
" Lustre flags on client boot where the binary may have been\n"
" corrupted in transit.\n"
).
-spec decode_safe(bitstring()) -> {ok, any()} | {error, decode_error()}.
decode_safe(Data) ->
libero_ffi:decode_safe(Data).
-file("src/libero/wire.gleam", 106).
?DOC(
" Parse a `{<<\"module_name\">>, toserver_value}` tuple from an ETF binary.\n"
" Returns the module name and the raw Dynamic value to be coerced.\n"
" Since `binary_to_term` returns real Erlang terms, no rebuild step\n"
" is needed - atoms are atoms, tuples are tuples, maps are maps.\n"
"\n"
" This is specifically for RPC call envelopes. For decoding\n"
" arbitrary values, use `decode`.\n"
).
-spec decode_call(bitstring()) -> {ok, {binary(), gleam@dynamic:dynamic_()}} |
{error, decode_error()}.
decode_call(Data) ->
libero_wire_ffi:decode_call(Data).
-file("src/libero/wire.gleam", 122).
?DOC(
" Encode a call envelope: `{module_name, msg}` as ETF binary.\n"
" Used by generated client send_to_server functions to pack a MsgFromClient value\n"
" for transport to the server.\n"
).
-spec encode_call(binary(), any()) -> bitstring().
encode_call(Module, Msg) ->
libero_ffi:encode({Module, Msg}).
-file("src/libero/wire.gleam", 129).
?DOC(" Tag a response frame so the JS client routes it to the FIFO callback queue.\n").
-spec tag_response(bitstring()) -> bitstring().
tag_response(Data) ->
<<0, Data/bitstring>>.
-file("src/libero/wire.gleam", 136).
?DOC(
" Tag a push frame so the JS client routes it to the push handler.\n"
" The value is an ETF-encoded `{module_name, msg}` tuple so the\n"
" client knows which handler to invoke.\n"
).
-spec tag_push(binary(), any()) -> bitstring().
tag_push(Module, Msg) ->
Data = libero_ffi:encode({Module, Msg}),
<<1, Data/bitstring>>.
-file("src/libero/wire.gleam", 157).
?DOC(
" Cast a Dynamic value to any type.\n"
" Used by generated server dispatch code to coerce the decoded\n"
" MsgFromClient value to its typed form. Safe when client and server are\n"
" built from the same source (the generator guarantees the types match).\n"
"\n"
" **Warning: unwitnessed cast.** Same safety model as `decode` —\n"
" type correctness depends on both sides being built from the same\n"
" source. A mismatch produces silent data corruption.\n"
"\n"
" This is by design — the generated code is the enforcement point.\n"
" Making this internal would break the generated dispatch modules\n"
" which live in consumer packages and need pub access.\n"
).
-spec coerce(gleam@dynamic:dynamic_()) -> any().
coerce(Value) ->
libero_ffi:identity(Value).