Current section
Files
Jump to
Current section
Files
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([decode_call/1, encode/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(
" Reflective JSON wire format for Libero RPC.\n"
"\n"
" The encoder walks any Gleam value via Erlang runtime introspection and\n"
" emits a JSON tree that preserves enough structure to rebuild Gleam\n"
" custom types on the client via a constructor registry.\n"
"\n"
" **Wire shape:**\n"
" - Primitives (Int, Float, Bool, String) → JSON primitives\n"
" - `Nil` / `None` → JSON `null`\n"
" - Gleam `List(a)` → JSON array of encoded elements\n"
" - Plain tuple `#(a, b, c)` → JSON array of encoded elements (no tag)\n"
" - Gleam custom type `Record(1, \"alice\", ...)` → `{\"@\": \"record\", \"v\": [1, \"alice\", ...]}`\n"
"\n"
" The distinction between \"Gleam custom type\" and \"plain tuple\" is made at\n"
" encode time: if the first element of a tuple is a non-boolean atom, the\n"
" tuple is treated as a custom type (where the atom is the constructor\n"
" name). Otherwise it's serialized as a plain array. This mirrors Gleam's\n"
" BEAM compilation: `Record(...)` becomes `{record, ...}` with the lowercase\n"
" atom in position 0.\n"
).
-type decode_error() :: {decode_error, binary(), gleam@json:decode_error()}.
-file("src/libero/wire.gleam", 66).
-spec atom_to_json(binary()) -> gleam@json:json().
atom_to_json(Name) ->
case Name of
<<"nil"/utf8>> ->
gleam@json:null();
_ ->
gleam@json:object(
[{<<"@"/utf8>>, gleam@json:string(Name)},
{<<"v"/utf8>>, gleam@json:preprocessed_array([])}]
)
end.
-file("src/libero/wire.gleam", 104).
?DOC(
" Parse `{\"fn\": \"records.save\", \"args\": [...]}` from text.\n"
" Args come back as a `List(Dynamic)`. The dispatch table coerces each\n"
" one to its expected type via `gleam_stdlib:identity`.\n"
).
-spec decode_call(binary()) -> {ok, {binary(), list(gleam@dynamic:dynamic_())}} |
{error, decode_error()}.
decode_call(Text) ->
Decoder = begin
gleam@dynamic@decode:field(
<<"fn"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Fn_name) ->
gleam@dynamic@decode:field(
<<"args"/utf8>>,
gleam@dynamic@decode:list(
{decoder, fun gleam@dynamic@decode:decode_dynamic/1}
),
fun(Args) ->
gleam@dynamic@decode:success({Fn_name, Args})
end
)
end
)
end,
_pipe = gleam@json:parse(Text, Decoder),
gleam@result:map_error(
_pipe,
fun(Err) -> {decode_error, <<"invalid call envelope"/utf8>>, Err} end
).
-file("src/libero/wire.gleam", 78).
-spec tuple_to_json(list(gleam@dynamic:dynamic_())) -> gleam@json:json().
tuple_to_json(Elements) ->
case Elements of
[] ->
gleam@json:preprocessed_array([]);
[First | Rest] ->
case erlang:is_atom(First) andalso not erlang:is_boolean(First) of
true ->
gleam@json:object(
[{<<"@"/utf8>>,
gleam@json:string(erlang:atom_to_binary(First))},
{<<"v"/utf8>>,
gleam@json:preprocessed_array(
gleam@list:map(Rest, fun walk/1)
)}]
);
false ->
gleam@json:preprocessed_array(
gleam@list:map(Elements, fun walk/1)
)
end
end.
-file("src/libero/wire.gleam", 34).
?DOC(" Walk any Erlang term and build a `gleam/json.Json` tree.\n").
-spec walk(gleam@dynamic:dynamic_()) -> gleam@json:json().
walk(Value) ->
Is_bool_v = erlang:is_boolean(Value),
Is_atom_v = erlang:is_atom(Value),
Is_int_v = erlang:is_integer(Value),
Is_float_v = erlang:is_float(Value),
Is_binary_v = erlang:is_binary(Value),
Is_list_v = erlang:is_list(Value),
Is_tuple_v = erlang:is_tuple(Value),
case {Is_bool_v,
Is_atom_v,
Is_int_v,
Is_float_v,
Is_binary_v,
Is_list_v,
Is_tuple_v} of
{true, _, _, _, _, _, _} ->
gleam@json:bool(gleam_stdlib:identity(Value));
{_, true, _, _, _, _, _} ->
atom_to_json(erlang:atom_to_binary(Value));
{_, _, true, _, _, _, _} ->
gleam@json:int(gleam_stdlib:identity(Value));
{_, _, _, true, _, _, _} ->
gleam@json:float(gleam_stdlib:identity(Value));
{_, _, _, _, true, _, _} ->
gleam@json:string(gleam_stdlib:identity(Value));
{_, _, _, _, _, true, _} ->
gleam@json:preprocessed_array(
gleam@list:map(gleam_stdlib:identity(Value), fun walk/1)
);
{_, _, _, _, _, _, true} ->
tuple_to_json(erlang:tuple_to_list(Value));
{_, _, _, _, _, _, _} ->
erlang:error(#{gleam_error => panic,
message => <<"wire_json: unsupported term in encoder"/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"libero/wire"/utf8>>,
function => <<"walk"/utf8>>,
line => 62})
end.
-file("src/libero/wire.gleam", 29).
-spec encode(any()) -> binary().
encode(Value) ->
gleam@json:to_string(walk(gleam_stdlib:identity(Value))).