Current section
Files
Jump to
Current section
Files
src/amf0.erl
-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
<<Value/float, Rest/bitstring>> ->
{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
<<Value, Rest/bitstring>> when Value =:= 1 ->
{ok, {{boolean, true}, Rest}};
<<Value@1, Rest@1/bitstring>> 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}};
<<Length:16, Bytes@1/bitstring>> ->
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, <<Length:16, Value/binary>>}
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) ->
<<Bs@1/bitstring, Value@1/bitstring>>
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) ->
<<Bs@1/bitstring,
Key@1/bitstring,
Value@1/bitstring>>
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
<<Marker, Rest/bitstring>> ->
_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
<<Length:32/unsigned, Rest/bitstring>> ->
_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.