-module(amf0). -compile([no_auto_import, nowarn_unused_vars]). -export([serialize/1, serialize_value/1, deserialize_value/1, deserialize/1]). -export_type([value/0, deserialize_error/0, serialize_error/0]). -type value() :: {number, float()} | {boolean, boolean()} | {string, binary()} | {object, gleam@map:map_(binary(), value())} | {strict_array, list(value())} | null | undefined. -type deserialize_error() :: missing_marker | {unknown_marker, integer()} | {unsupported_marker, amf0@marker:marker()} | {malformed_input, amf0@marker:marker()}. -type serialize_error() :: string_too_long | strict_array_too_long. -spec deserialize_number(bitstring()) -> {ok, {value(), bitstring()}} | {error, deserialize_error()}. deserialize_number(Bytes) -> case Bytes of <> -> {ok, {{number, Value}, Rest}}; _ -> {error, {malformed_input, number}} end. -spec deserialize_bool(bitstring()) -> {ok, {value(), bitstring()}} | {error, deserialize_error()}. deserialize_bool(Bytes) -> case Bytes of <> when Value =:= 1 -> {ok, {{boolean, true}, Rest}}; <> when Value@1 =:= 0 -> {ok, {{boolean, false}, Rest@1}}; _ -> {error, {malformed_input, boolean}} end. -spec deserialize_utf8_string(bitstring()) -> {ok, {binary(), bitstring()}} | {error, deserialize_error()}. deserialize_utf8_string(Bytes) -> case Bytes of <<0:16, Rest/bitstring>> -> {ok, {<<""/utf8>>, Rest}}; <> -> gleam@result:'try'( begin _pipe = gleam@bit_string:slice(Bytes@1, 0, Length), _pipe@1 = gleam@result:'try'( _pipe, fun gleam@bit_string:to_string/1 ), gleam@result:replace_error( _pipe@1, {malformed_input, string} ) end, fun(String) -> Rest@1 = begin _pipe@2 = gleam@bit_string:slice( Bytes@1, Length, gleam@bit_string:byte_size(Bytes@1) - Length ), gleam@result:unwrap(_pipe@2, <<>>) end, {ok, {String, Rest@1}} end ); _ -> {error, {malformed_input, string}} end. -spec deserialize_string(bitstring()) -> {ok, {value(), bitstring()}} | {error, deserialize_error()}. deserialize_string(Bytes) -> _pipe = deserialize_utf8_string(Bytes), gleam@result:map( _pipe, fun(_capture) -> gleam@pair:map_first( _capture, fun(Field@0) -> {string, Field@0} end ) end ). -spec deserialize_null(bitstring()) -> {ok, {value(), bitstring()}} | {error, deserialize_error()}. deserialize_null(Bytes) -> {ok, {null, Bytes}}. -spec deserialize_undefined(bitstring()) -> {ok, {value(), bitstring()}} | {error, deserialize_error()}. deserialize_undefined(Bytes) -> {ok, {undefined, Bytes}}. -spec serialize_number(float()) -> bitstring(). serialize_number(Value) -> <<(amf0@marker:to_int(number)), Value/float>>. -spec serialize_bool(boolean()) -> bitstring(). serialize_bool(Value) -> <<(amf0@marker:to_int(boolean)), (gleam@bool:to_int(Value))>>. -spec serialize_utf8(binary()) -> {ok, bitstring()} | {error, serialize_error()}. serialize_utf8(Value) -> case gleam@string:byte_size(Value) of X when X > 65535 -> {error, string_too_long}; Length -> {ok, <>} end. -spec serialize_string(binary()) -> {ok, bitstring()} | {error, serialize_error()}. serialize_string(Value) -> _pipe = serialize_utf8(Value), gleam@result:map( _pipe, fun(Utf8) -> <<(amf0@marker:to_int(string)), Utf8/bitstring>> end ). -spec serialize_null() -> bitstring(). serialize_null() -> <<(amf0@marker:to_int(null))>>. -spec serialize_undefined() -> bitstring(). serialize_undefined() -> <<(amf0@marker:to_int(undefined))>>. -spec serialize_strict_array(list(value())) -> {ok, bitstring()} | {error, serialize_error()}. serialize_strict_array(List) -> case gleam@list:length(List) of X when X > 4294967295 -> {error, strict_array_too_long}; _ -> gleam@result:map( serialize(List), fun(Items) -> <<(amf0@marker:to_int(strict_array)), (gleam@list:length(List)):32, Items/bitstring>> end ) end. -spec serialize(list(value())) -> {ok, bitstring()} | {error, serialize_error()}. serialize(Values) -> gleam@list:fold( Values, {ok, <<>>}, fun(Bs, Value) -> gleam@result:'try'( Bs, fun(Bs@1) -> gleam@result:map( serialize_value(Value), fun(Value@1) -> <> end ) end ) end ). -spec serialize_value(value()) -> {ok, bitstring()} | {error, serialize_error()}. serialize_value(Value) -> case Value of {number, Val} -> {ok, serialize_number(Val)}; {boolean, Val@1} -> {ok, serialize_bool(Val@1)}; {string, Val@2} -> serialize_string(Val@2); {object, Val@3} -> serialize_object(Val@3); {strict_array, Val@4} -> serialize_strict_array(Val@4); null -> {ok, serialize_null()}; undefined -> {ok, serialize_undefined()} end. -spec serialize_properties(gleam@map:map_(binary(), value())) -> {ok, bitstring()} | {error, serialize_error()}. serialize_properties(Properties) -> gleam@map:fold( Properties, {ok, <<>>}, fun(Bs, Key, Value) -> gleam@result:'try'( Bs, fun(Bs@1) -> gleam@result:'try'( serialize_utf8(Key), fun(Key@1) -> gleam@result:map( serialize_value(Value), fun(Value@1) -> <> end ) end ) end ) end ). -spec serialize_object(gleam@map:map_(binary(), value())) -> {ok, bitstring()} | {error, serialize_error()}. serialize_object(Value) -> gleam@result:map( serialize_properties(Value), fun(Properties) -> <<(amf0@marker:to_int(object)), Properties/bitstring, 0:16, (amf0@marker:to_int(object_end))>> end ). -spec deserialize_object_property(bitstring()) -> {ok, {gleam@option:option({binary(), value()}), bitstring()}} | {error, deserialize_error()}. deserialize_object_property(Bytes) -> gleam@result:'try'( deserialize_utf8_string(Bytes), fun(_use0) -> {Key, Bytes@1} = _use0, case Key of <<""/utf8>> -> {ok, {none, Bytes@1}}; _ -> _pipe = deserialize_value(Bytes@1), _pipe@1 = gleam@result:map( _pipe, fun(_capture) -> gleam@pair:map_first( _capture, fun(_capture@1) -> gleam@pair:new(Key, _capture@1) end ) end ), gleam@result:map( _pipe@1, fun(_capture@2) -> gleam@pair:map_first( _capture@2, fun(Field@0) -> {some, Field@0} end ) end ) end end ). -spec deserialize_value(bitstring()) -> {ok, {value(), bitstring()}} | {error, deserialize_error()}. deserialize_value(Bytes) -> case Bytes of <> -> _pipe = amf0@marker:from_int(Marker), _pipe@1 = gleam@result:map_error( _pipe, fun(Field@0) -> {unknown_marker, Field@0} end ), gleam@result:'try'( _pipe@1, fun(Marker@1) -> deserialize_value_from_marker(Marker@1, Rest) end ); _ -> {error, missing_marker} end. -spec deserialize_value_from_marker(amf0@marker:marker(), bitstring()) -> {ok, {value(), bitstring()}} | {error, deserialize_error()}. deserialize_value_from_marker(Marker, Bytes) -> case Marker of number -> deserialize_number(Bytes); boolean -> deserialize_bool(Bytes); string -> deserialize_string(Bytes); object -> deserialize_object(Bytes); strict_array -> deserialize_strict_array(Bytes); null -> deserialize_null(Bytes); undefined -> deserialize_undefined(Bytes); _ -> {error, {unsupported_marker, Marker}} end. -spec deserialize_object(bitstring()) -> {ok, {value(), bitstring()}} | {error, deserialize_error()}. deserialize_object(Bytes) -> gleam@result:'try'( deserialize_object_properties(Bytes, gleam@map:new()), fun(_use0) -> {Properties, Bytes@1} = _use0, case Bytes@1 of <<0:16, ""/utf8, 16#09, Rest/bitstring>> -> {ok, {{object, Properties}, Rest}}; _ -> {error, {malformed_input, object}} end end ). -spec deserialize_object_properties( bitstring(), gleam@map:map_(binary(), value()) ) -> {ok, {gleam@map:map_(binary(), value()), bitstring()}} | {error, deserialize_error()}. deserialize_object_properties(Bytes, Properties) -> case deserialize_object_property(Bytes) of {ok, {{some, {Key, Value}}, Rest}} -> deserialize_object_properties( Rest, gleam@map:insert(Properties, Key, Value) ); {ok, {none, _}} -> {ok, {Properties, Bytes}}; {error, Error} -> {error, Error} end. -spec deserialize_next(bitstring(), list(value())) -> {ok, list(value())} | {error, deserialize_error()}. deserialize_next(Bytes, List) -> case Bytes of <<>> -> {ok, gleam@list:reverse(List)}; _ -> gleam@result:'try'( deserialize_value(Bytes), fun(_use0) -> {Value, Rest} = _use0, deserialize_next(Rest, [Value | List]) end ) end. -spec deserialize(bitstring()) -> {ok, list(value())} | {error, deserialize_error()}. deserialize(Bytes) -> deserialize_next(Bytes, []). -spec deserialize_n_values(bitstring(), integer(), list(value())) -> {ok, {list(value()), bitstring()}} | {error, deserialize_error()}. deserialize_n_values(Bytes, Remaining, List) -> case Remaining of 0 -> {ok, {List, Bytes}}; _ -> gleam@result:'try'( deserialize_value(Bytes), fun(_use0) -> {Value, Rest} = _use0, deserialize_n_values(Rest, Remaining - 1, [Value | List]) end ) end. -spec deserialize_strict_array(bitstring()) -> {ok, {value(), bitstring()}} | {error, deserialize_error()}. deserialize_strict_array(Bytes) -> case Bytes of <> -> _pipe = deserialize_n_values(Rest, Length, []), _pipe@1 = gleam@result:map( _pipe, fun(_capture) -> gleam@pair:map_first(_capture, fun gleam@list:reverse/1) end ), gleam@result:map( _pipe@1, fun(_capture@1) -> gleam@pair:map_first( _capture@1, fun(Field@0) -> {strict_array, Field@0} end ) end ); _ -> {error, {malformed_input, strict_array}} end.