-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/4, encode_snapshot/3, encode_patch/3, encode_command/5, encode_commands/3, encode_widget_op/4, encode_load_font/4, encode_subscribe/6, encode_unsubscribe/4, encode_window_op/5, encode_system_op/4, encode_system_query/4, encode_effect/5, encode_image_op/4, encode_advance_frame/3, encode_register_effect_stub/4, encode_unregister_effect_stub/3, encode_interact/6]). -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", 33). -spec is_non_finite_float(float()) -> boolean(). is_non_finite_float(Value) -> not plushie_ffi:is_finite_float(Value). -file("src/plushie/protocol/encode.gleam", 37). -spec float_to_json(float()) -> gleam@json:json(). float_to_json(Value) -> case is_non_finite_float(Value) of true -> gleam@json:null(); false -> gleam@json:float(Value) end. -file("src/plushie/protocol/encode.gleam", 45). -spec float_to_msgpack(float()) -> glepack@data:value(). float_to_msgpack(Value) -> case is_non_finite_float(Value) of true -> nil; false -> {float, Value} end. -file("src/plushie/protocol/encode.gleam", 53). ?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} -> float_to_json(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); {opaque_val, _} -> gleam@json:null() end. -file("src/plushie/protocol/encode.gleam", 73). ?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_to_msgpack(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)}; {opaque_val, _} -> nil; {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", 97). ?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", 124). -spec serialize_json(gleam@dict:dict(binary(), plushie@node:prop_value())) -> {ok, bitstring()} | {error, plushie@protocol:encode_error()}. serialize_json(Message) -> 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)}. -file("src/plushie/protocol/encode.gleam", 136). -spec serialize_msgpack(gleam@dict:dict(binary(), plushie@node:prop_value())) -> {ok, bitstring()} | {error, plushie@protocol:encode_error()}. serialize_msgpack(Message) -> 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. -file("src/plushie/protocol/encode.gleam", 114). ?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 -> serialize_json(Message); msgpack -> serialize_msgpack(Message) end. -file("src/plushie/protocol/encode.gleam", 197). ?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", 158). ?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", 204). ?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", 297). -spec normalize_default_font(plushie@node:prop_value()) -> plushie@node:prop_value(). normalize_default_font(Font) -> case Font of {string_val, Family} -> {dict_val, maps:from_list([{<<"family"/utf8>>, {string_val, Family}}])}; Other -> Other end. -file("src/plushie/protocol/encode.gleam", 221). ?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(), gleam@option:option(binary()) ) -> {ok, bitstring()} | {error, plushie@protocol:encode_error()}. encode_settings(Settings, Session, Format, Token) -> 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>>, normalize_default_font(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, Settings_fields@5 = case erlang:element(10, Settings) of true -> [{<<"validate_props"/utf8>>, {bool_val, true}} | Settings_fields@4]; false -> Settings_fields@4 end, Settings_fields@6 = case Token of {some, T@1} -> [{<<"token_sha256"/utf8>>, {string_val, plushie_ffi:sha256_hex(gleam_stdlib:identity(T@1))}} | Settings_fields@5]; none -> Settings_fields@5 end, Settings_fields@7 = case gleam@dict:is_empty(erlang:element(11, Settings)) of true -> Settings_fields@6; false -> [{<<"widget_config"/utf8>>, {dict_val, erlang:element(11, Settings)}} | Settings_fields@6] end, Settings_fields@8 = case erlang:element(12, Settings) of [] -> Settings_fields@7; Items -> [{<<"required_widgets"/utf8>>, {list_val, gleam@list:map( Items, fun(Field@0) -> {string_val, Field@0} end )}} | Settings_fields@7] end, Fields = [{<<"settings"/utf8>>, {dict_val, maps:from_list(Settings_fields@8)}}], serialize(message(<<"settings"/utf8>>, Session, Fields), Format). -file("src/plushie/protocol/encode.gleam", 306). ?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", 316). ?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", 329). ?DOC( " Encode a widget-targeted command using the unified format.\n" " Wire: {type: \"command\", session, id, family, value}\n" ). -spec encode_command( binary(), binary(), gleam@dict:dict(binary(), plushie@node:prop_value()), binary(), plushie@protocol:format() ) -> {ok, bitstring()} | {error, plushie@protocol:encode_error()}. encode_command(Id, Family, Value, Session, Format) -> Value_pv = case gleam@dict:is_empty(Value) of true -> null_val; false -> {dict_val, Value} end, Fields = [{<<"id"/utf8>>, {string_val, Id}}, {<<"family"/utf8>>, {string_val, Family}}, {<<"value"/utf8>>, Value_pv}], serialize(message(<<"command"/utf8>>, Session, Fields), Format). -file("src/plushie/protocol/encode.gleam", 350). ?DOC( " Encode a batch of widget-targeted commands.\n" " Wire: {type: \"commands\", session, commands: [{id, family, value}, ...]}\n" ). -spec encode_commands( list({binary(), binary(), gleam@dict:dict(binary(), plushie@node:prop_value())}), binary(), plushie@protocol:format() ) -> {ok, bitstring()} | {error, plushie@protocol:encode_error()}. encode_commands(Commands, Session, Format) -> Items = gleam@list:map( Commands, fun(Cmd) -> {Id, Family, Value} = Cmd, Value_pv = case gleam@dict:is_empty(Value) of true -> null_val; false -> {dict_val, Value} end, {dict_val, maps:from_list( [{<<"id"/utf8>>, {string_val, Id}}, {<<"family"/utf8>>, {string_val, Family}}, {<<"value"/utf8>>, Value_pv}] )} end ), Fields = [{<<"commands"/utf8>>, {list_val, Items}}], serialize(message(<<"commands"/utf8>>, Session, Fields), Format). -file("src/plushie/protocol/encode.gleam", 375). ?DOC(" Encode a global widget operation (no target ID: focus_next, announce, 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", 390). ?DOC( " Encode a load_font message.\n" "\n" " `data` carries the font bytes. JSON encodes them as a base64 string;\n" " MessagePack encodes them as a native binary value. `BinaryVal` handles\n" " both representations.\n" ). -spec encode_load_font( binary(), bitstring(), binary(), plushie@protocol:format() ) -> {ok, bitstring()} | {error, plushie@protocol:encode_error()}. encode_load_font(Family, Data, Session, Format) -> Fields = [{<<"family"/utf8>>, {string_val, Family}}, {<<"data"/utf8>>, {binary_val, Data}}], serialize(message(<<"load_font"/utf8>>, Session, Fields), Format). -file("src/plushie/protocol/encode.gleam", 401). ?DOC(" Encode a subscribe message to start an event source.\n"). -spec encode_subscribe( binary(), binary(), gleam@option:option(integer()), gleam@option:option(binary()), binary(), plushie@protocol:format() ) -> {ok, bitstring()} | {error, plushie@protocol:encode_error()}. encode_subscribe(Kind, Tag, Max_rate, Window_id, 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, Fields@2 = case Window_id of {some, Wid} -> lists:append(Fields@1, [{<<"window_id"/utf8>>, {string_val, Wid}}]); none -> Fields@1 end, serialize(message(<<"subscribe"/utf8>>, Session, Fields@2), Format). -file("src/plushie/protocol/encode.gleam", 424). ?DOC( " Encode an unsubscribe message to stop an event source.\n" " Includes the tag for targeted unsubscription when multiple\n" " subscriptions of the same kind exist (e.g. window-scoped).\n" ). -spec encode_unsubscribe( binary(), binary(), binary(), plushie@protocol:format() ) -> {ok, bitstring()} | {error, plushie@protocol:encode_error()}. encode_unsubscribe(Kind, Tag, Session, Format) -> Fields = [{<<"kind"/utf8>>, {string_val, Kind}}, {<<"tag"/utf8>>, {string_val, Tag}}], serialize(message(<<"unsubscribe"/utf8>>, Session, Fields), Format). -file("src/plushie/protocol/encode.gleam", 438). ?DOC( " Encode a window operation (open, close, configure).\n" "\n" " Uses the unified `_op` envelope: op-specific data lives under\n" " `payload`; the `window_id` addressing field stays flat beside `op`.\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, Payload, Session, Format) -> Fields = [{<<"op"/utf8>>, {string_val, Op}}, {<<"window_id"/utf8>>, {string_val, Window_id}}, {<<"payload"/utf8>>, {dict_val, Payload}}], serialize(message(<<"window_op"/utf8>>, Session, Fields), Format). -file("src/plushie/protocol/encode.gleam", 456). ?DOC( " Encode a system-wide operation.\n" "\n" " Uses the unified `_op` envelope: op-specific data lives under `payload`.\n" ). -spec encode_system_op( binary(), gleam@dict:dict(binary(), plushie@node:prop_value()), binary(), plushie@protocol:format() ) -> {ok, bitstring()} | {error, plushie@protocol:encode_error()}. encode_system_op(Op, Payload, Session, Format) -> Fields = [{<<"op"/utf8>>, {string_val, Op}}, {<<"payload"/utf8>>, {dict_val, Payload}}], serialize(message(<<"system_op"/utf8>>, Session, Fields), Format). -file("src/plushie/protocol/encode.gleam", 472). ?DOC( " Encode a system-wide query.\n" "\n" " Uses the unified `_op` envelope: query-specific data lives under `payload`.\n" ). -spec encode_system_query( binary(), gleam@dict:dict(binary(), plushie@node:prop_value()), binary(), plushie@protocol:format() ) -> {ok, bitstring()} | {error, plushie@protocol:encode_error()}. encode_system_query(Op, Payload, Session, Format) -> Fields = [{<<"op"/utf8>>, {string_val, Op}}, {<<"payload"/utf8>>, {dict_val, Payload}}], serialize(message(<<"system_query"/utf8>>, Session, Fields), Format). -file("src/plushie/protocol/encode.gleam", 486). ?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", 505). ?DOC( " Encode an image operation (create_image, update_image, delete_image).\n" "\n" " Uses the unified `_op` envelope: op-specific data (including `handle`,\n" " `data`, `pixels`, `width`, `height`) lives under `payload`.\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) -> Fields = [{<<"op"/utf8>>, {string_val, Op}}, {<<"payload"/utf8>>, {dict_val, Payload}}], serialize(message(<<"image_op"/utf8>>, Session, Fields), Format). -file("src/plushie/protocol/encode.gleam", 519). ?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). -file("src/plushie/protocol/encode.gleam", 532). ?DOC( " Encode an effect stub registration.\n" "\n" " The renderer will return the given response value immediately\n" " for any effect of this kind, without executing the real effect.\n" ). -spec encode_register_effect_stub( binary(), plushie@node:prop_value(), binary(), plushie@protocol:format() ) -> {ok, bitstring()} | {error, plushie@protocol:encode_error()}. encode_register_effect_stub(Kind, Response, Session, Format) -> Fields = [{<<"kind"/utf8>>, {string_val, Kind}}, {<<"response"/utf8>>, Response}], serialize(message(<<"register_effect_stub"/utf8>>, Session, Fields), Format). -file("src/plushie/protocol/encode.gleam", 543). ?DOC(" Encode an effect stub removal.\n"). -spec encode_unregister_effect_stub( binary(), binary(), plushie@protocol:format() ) -> {ok, bitstring()} | {error, plushie@protocol:encode_error()}. encode_unregister_effect_stub(Kind, Session, Format) -> Fields = [{<<"kind"/utf8>>, {string_val, Kind}}], serialize( message(<<"unregister_effect_stub"/utf8>>, Session, Fields), Format ). -file("src/plushie/protocol/encode.gleam", 556). ?DOC( " Encode an interact request (click, type_text, press, etc.).\n" "\n" " Used by the scripting engine and testing infrastructure to perform\n" " renderer-side interactions on the widget tree.\n" ). -spec encode_interact( binary(), binary(), gleam@dict:dict(binary(), plushie@node:prop_value()), gleam@dict:dict(binary(), plushie@node:prop_value()), binary(), plushie@protocol:format() ) -> {ok, bitstring()} | {error, plushie@protocol:encode_error()}. encode_interact(Id, Action, Selector, Payload, Session, Format) -> Fields = [{<<"id"/utf8>>, {string_val, Id}}, {<<"action"/utf8>>, {string_val, Action}}, {<<"selector"/utf8>>, {dict_val, Selector}}, {<<"payload"/utf8>>, {dict_val, Payload}}], serialize(message(<<"interact"/utf8>>, Session, Fields), Format).