-module(plushie@protocol@encode). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/plushie/protocol/encode.gleam"). -export([prop_value_to_json/1, prop_value_to_msgpack/1, node_to_prop_value/1, serialize/2, patch_op_to_prop_value/1, encode_settings/3, encode_snapshot/3, encode_patch/3, encode_widget_op/4, encode_subscribe/5, encode_unsubscribe/3, encode_window_op/5, encode_effect/5, encode_image_op/4, encode_extension_command/5, encode_advance_frame/3]). -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( " Encode outbound messages for the plushie wire protocol.\n" "\n" " Each function encodes a specific message type to wire format\n" " (JSONL or MessagePack bytes). All outbound messages include a\n" " \"type\" field identifying the message kind and a \"session\" field\n" " for multiplexed session support.\n" ). -file("src/plushie/protocol/encode.gleam", 31). ?DOC(" Convert a PropValue to gleam_json's Json type.\n"). -spec prop_value_to_json(plushie@node:prop_value()) -> gleam@json:json(). prop_value_to_json(V) -> case V of {string_val, S} -> gleam@json:string(S); {int_val, N} -> gleam@json:int(N); {float_val, F} -> gleam@json:float(F); {bool_val, B} -> gleam@json:bool(B); null_val -> gleam@json:null(); {binary_val, Bytes} -> gleam@json:string(gleam_stdlib:base64_encode(Bytes, true)); {list_val, Items} -> gleam@json:preprocessed_array( gleam@list:map(Items, fun prop_value_to_json/1) ); {dict_val, D} -> _pipe = maps:to_list(D), _pipe@1 = gleam@list:map( _pipe, fun(Pair) -> {erlang:element(1, Pair), prop_value_to_json(erlang:element(2, Pair))} end ), gleam@json:object(_pipe@1) end. -file("src/plushie/protocol/encode.gleam", 49). ?DOC(" Convert a PropValue to glepack's data.Value type.\n"). -spec prop_value_to_msgpack(plushie@node:prop_value()) -> glepack@data:value(). prop_value_to_msgpack(V) -> case V of {string_val, S} -> {string, S}; {int_val, N} -> {integer, N}; {float_val, F} -> {float, F}; {bool_val, B} -> {boolean, B}; null_val -> nil; {binary_val, Bytes} -> {binary, Bytes}; {list_val, Items} -> {array, gleam@list:map(Items, fun prop_value_to_msgpack/1)}; {dict_val, D} -> _pipe = maps:to_list(D), _pipe@1 = gleam@list:map( _pipe, fun(Pair) -> {{string, erlang:element(1, Pair)}, prop_value_to_msgpack(erlang:element(2, Pair))} end ), _pipe@2 = maps:from_list(_pipe@1), {map, _pipe@2} end. -file("src/plushie/protocol/encode.gleam", 72). ?DOC( " Convert a Node tree to a nested PropValue (DictVal).\n" " Maps `kind` to the wire key `\"type\"`.\n" ). -spec node_to_prop_value(plushie@node:node_()) -> plushie@node:prop_value(). node_to_prop_value(N) -> {dict_val, maps:from_list( [{<<"id"/utf8>>, {string_val, erlang:element(2, N)}}, {<<"type"/utf8>>, {string_val, erlang:element(3, N)}}, {<<"props"/utf8>>, {dict_val, erlang:element(4, N)}}, {<<"children"/utf8>>, {list_val, gleam@list:map( erlang:element(5, N), fun node_to_prop_value/1 )}}] )}. -file("src/plushie/protocol/encode.gleam", 87). ?DOC( " Serialize a message (represented as a Dict of PropValues) to wire bytes.\n" " JSON format appends a newline; MessagePack produces raw bytes.\n" ). -spec serialize( gleam@dict:dict(binary(), plushie@node:prop_value()), plushie@protocol:format() ) -> {ok, bitstring()} | {error, plushie@protocol:encode_error()}. serialize(Message, Format) -> case Format of json -> J = begin _pipe = maps:to_list(Message), _pipe@1 = gleam@list:map( _pipe, fun(Pair) -> {erlang:element(1, Pair), prop_value_to_json(erlang:element(2, Pair))} end ), gleam@json:object(_pipe@1) end, S = <<(gleam@json:to_string(J))/binary, "\n"/utf8>>, {ok, gleam_stdlib:identity(S)}; msgpack -> V = prop_value_to_msgpack({dict_val, Message}), case glepack:pack(V) of {ok, Bytes} -> {ok, Bytes}; {error, _} -> {error, {serialization_failed, <<"msgpack encoding failed"/utf8>>}} end end. -file("src/plushie/protocol/encode.gleam", 154). ?DOC(" Encode a path (list of child indices) to a PropValue array.\n"). -spec path_to_prop_value(list(integer())) -> plushie@node:prop_value(). path_to_prop_value(Path) -> {list_val, gleam@list:map(Path, fun(Field@0) -> {int_val, Field@0} end)}. -file("src/plushie/protocol/encode.gleam", 115). ?DOC( " Convert a PatchOp to its wire PropValue representation.\n" "\n" " Paths are encoded as arrays of integers on the wire.\n" ). -spec patch_op_to_prop_value(plushie@patch:patch_op()) -> plushie@node:prop_value(). patch_op_to_prop_value(Op) -> case Op of {replace_node, Path, Node} -> {dict_val, maps:from_list( [{<<"op"/utf8>>, {string_val, <<"replace_node"/utf8>>}}, {<<"path"/utf8>>, path_to_prop_value(Path)}, {<<"node"/utf8>>, node_to_prop_value(Node)}] )}; {update_props, Path@1, Props} -> {dict_val, maps:from_list( [{<<"op"/utf8>>, {string_val, <<"update_props"/utf8>>}}, {<<"path"/utf8>>, path_to_prop_value(Path@1)}, {<<"props"/utf8>>, {dict_val, Props}}] )}; {insert_child, Path@2, Index, Node@1} -> {dict_val, maps:from_list( [{<<"op"/utf8>>, {string_val, <<"insert_child"/utf8>>}}, {<<"path"/utf8>>, path_to_prop_value(Path@2)}, {<<"index"/utf8>>, {int_val, Index}}, {<<"node"/utf8>>, node_to_prop_value(Node@1)}] )}; {remove_child, Path@3, Index@1} -> {dict_val, maps:from_list( [{<<"op"/utf8>>, {string_val, <<"remove_child"/utf8>>}}, {<<"path"/utf8>>, path_to_prop_value(Path@3)}, {<<"index"/utf8>>, {int_val, Index@1}}] )} end. -file("src/plushie/protocol/encode.gleam", 161). ?DOC(" Build a message Dict with common fields.\n"). -spec message(binary(), binary(), list({binary(), plushie@node:prop_value()})) -> gleam@dict:dict(binary(), plushie@node:prop_value()). message(Msg_type, Session, Fields) -> maps:from_list( [{<<"type"/utf8>>, {string_val, Msg_type}}, {<<"session"/utf8>>, {string_val, Session}} | Fields] ). -file("src/plushie/protocol/encode.gleam", 178). ?DOC( " Encode a settings message sent on startup.\n" "\n" " Settings are wrapped in a `\"settings\"` key in the wire message,\n" " matching the Rust binary's expected format:\n" " `{\"type\": \"settings\", \"session\": \"\", \"settings\": {...}}`\n" ). -spec encode_settings( plushie@app:settings(), binary(), plushie@protocol:format() ) -> {ok, bitstring()} | {error, plushie@protocol:encode_error()}. encode_settings(Settings, Session, Format) -> Settings_fields = [{<<"protocol_version"/utf8>>, {int_val, 1}}, {<<"antialiasing"/utf8>>, {bool_val, erlang:element(2, Settings)}}, {<<"default_text_size"/utf8>>, {float_val, erlang:element(3, Settings)}}, {<<"vsync"/utf8>>, {bool_val, erlang:element(6, Settings)}}, {<<"scale_factor"/utf8>>, {float_val, erlang:element(7, Settings)}}], Settings_fields@1 = case erlang:element(4, Settings) of {some, T} -> [{<<"theme"/utf8>>, plushie@prop@theme:to_prop_value(T)} | Settings_fields]; none -> Settings_fields end, Settings_fields@2 = case erlang:element(5, Settings) of [] -> Settings_fields@1; Fonts -> [{<<"fonts"/utf8>>, {list_val, gleam@list:map( Fonts, fun(Field@0) -> {string_val, Field@0} end )}} | Settings_fields@1] end, Settings_fields@3 = case erlang:element(8, Settings) of {some, Font} -> [{<<"default_font"/utf8>>, Font} | Settings_fields@2]; none -> Settings_fields@2 end, Settings_fields@4 = case erlang:element(9, Settings) of {some, Rate} -> [{<<"default_event_rate"/utf8>>, {int_val, Rate}} | Settings_fields@3]; none -> Settings_fields@3 end, Fields = [{<<"settings"/utf8>>, {dict_val, maps:from_list(Settings_fields@4)}}], serialize(message(<<"settings"/utf8>>, Session, Fields), Format). -file("src/plushie/protocol/encode.gleam", 217). ?DOC(" Encode a full tree snapshot.\n"). -spec encode_snapshot(plushie@node:node_(), binary(), plushie@protocol:format()) -> {ok, bitstring()} | {error, plushie@protocol:encode_error()}. encode_snapshot(Tree, Session, Format) -> Fields = [{<<"tree"/utf8>>, node_to_prop_value(Tree)}], serialize(message(<<"snapshot"/utf8>>, Session, Fields), Format). -file("src/plushie/protocol/encode.gleam", 227). ?DOC(" Encode an incremental patch (list of diff operations).\n"). -spec encode_patch( list(plushie@patch:patch_op()), binary(), plushie@protocol:format() ) -> {ok, bitstring()} | {error, plushie@protocol:encode_error()}. encode_patch(Ops, Session, Format) -> Fields = [{<<"ops"/utf8>>, {list_val, gleam@list:map(Ops, fun patch_op_to_prop_value/1)}}], serialize(message(<<"patch"/utf8>>, Session, Fields), Format). -file("src/plushie/protocol/encode.gleam", 239). ?DOC(" Encode a widget operation (focus, scroll, etc.).\n"). -spec encode_widget_op( binary(), gleam@dict:dict(binary(), plushie@node:prop_value()), binary(), plushie@protocol:format() ) -> {ok, bitstring()} | {error, plushie@protocol:encode_error()}. encode_widget_op(Op, Payload, Session, Format) -> Fields = [{<<"op"/utf8>>, {string_val, Op}}, {<<"payload"/utf8>>, {dict_val, Payload}}], serialize(message(<<"widget_op"/utf8>>, Session, Fields), Format). -file("src/plushie/protocol/encode.gleam", 250). ?DOC(" Encode a subscribe message to start an event source.\n"). -spec encode_subscribe( binary(), binary(), gleam@option:option(integer()), binary(), plushie@protocol:format() ) -> {ok, bitstring()} | {error, plushie@protocol:encode_error()}. encode_subscribe(Kind, Tag, Max_rate, Session, Format) -> Fields = [{<<"kind"/utf8>>, {string_val, Kind}}, {<<"tag"/utf8>>, {string_val, Tag}}], Fields@1 = case Max_rate of {some, Rate} -> lists:append(Fields, [{<<"max_rate"/utf8>>, {int_val, Rate}}]); none -> Fields end, serialize(message(<<"subscribe"/utf8>>, Session, Fields@1), Format). -file("src/plushie/protocol/encode.gleam", 266). ?DOC(" Encode an unsubscribe message to stop an event source.\n"). -spec encode_unsubscribe(binary(), binary(), plushie@protocol:format()) -> {ok, bitstring()} | {error, plushie@protocol:encode_error()}. encode_unsubscribe(Kind, Session, Format) -> Fields = [{<<"kind"/utf8>>, {string_val, Kind}}], serialize(message(<<"unsubscribe"/utf8>>, Session, Fields), Format). -file("src/plushie/protocol/encode.gleam", 276). ?DOC(" Encode a window operation (open, close, configure).\n"). -spec encode_window_op( binary(), binary(), gleam@dict:dict(binary(), plushie@node:prop_value()), binary(), plushie@protocol:format() ) -> {ok, bitstring()} | {error, plushie@protocol:encode_error()}. encode_window_op(Op, Window_id, Settings, Session, Format) -> Fields = [{<<"op"/utf8>>, {string_val, Op}}, {<<"window_id"/utf8>>, {string_val, Window_id}}, {<<"settings"/utf8>>, {dict_val, Settings}}], serialize(message(<<"window_op"/utf8>>, Session, Fields), Format). -file("src/plushie/protocol/encode.gleam", 292). ?DOC(" Encode a platform effect request (file dialog, clipboard, etc.).\n"). -spec encode_effect( binary(), binary(), gleam@dict:dict(binary(), plushie@node:prop_value()), binary(), plushie@protocol:format() ) -> {ok, bitstring()} | {error, plushie@protocol:encode_error()}. encode_effect(Id, Kind, Payload, Session, Format) -> Fields = [{<<"id"/utf8>>, {string_val, Id}}, {<<"kind"/utf8>>, {string_val, Kind}}, {<<"payload"/utf8>>, {dict_val, Payload}}], serialize(message(<<"effect"/utf8>>, Session, Fields), Format). -file("src/plushie/protocol/encode.gleam", 311). ?DOC( " Encode an image operation (create_image, update_image, delete_image).\n" "\n" " Payload fields are flat-merged into the top-level message dict\n" " (not nested under \"payload\"), matching the Elixir reference.\n" ). -spec encode_image_op( binary(), gleam@dict:dict(binary(), plushie@node:prop_value()), binary(), plushie@protocol:format() ) -> {ok, bitstring()} | {error, plushie@protocol:encode_error()}. encode_image_op(Op, Payload, Session, Format) -> Base = message( <<"image_op"/utf8>>, Session, [{<<"op"/utf8>>, {string_val, Op}}] ), Merged = maps:merge(Base, Payload), serialize(Merged, Format). -file("src/plushie/protocol/encode.gleam", 323). ?DOC(" Encode an extension command for custom widget operations.\n"). -spec encode_extension_command( binary(), binary(), gleam@dict:dict(binary(), plushie@node:prop_value()), binary(), plushie@protocol:format() ) -> {ok, bitstring()} | {error, plushie@protocol:encode_error()}. encode_extension_command(Node_id, Op, Payload, Session, Format) -> Fields = [{<<"node_id"/utf8>>, {string_val, Node_id}}, {<<"op"/utf8>>, {string_val, Op}}, {<<"payload"/utf8>>, {dict_val, Payload}}], serialize(message(<<"extension_command"/utf8>>, Session, Fields), Format). -file("src/plushie/protocol/encode.gleam", 339). ?DOC(" Encode a frame advance (test/headless mode).\n"). -spec encode_advance_frame(integer(), binary(), plushie@protocol:format()) -> {ok, bitstring()} | {error, plushie@protocol:encode_error()}. encode_advance_frame(Timestamp, Session, Format) -> Fields = [{<<"timestamp"/utf8>>, {int_val, Timestamp}}], serialize(message(<<"advance_frame"/utf8>>, Session, Fields), Format).