Current section
Files
Jump to
Current section
Files
src/scamper@serialization.erl
-module(scamper@serialization).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/scamper/serialization.gleam").
-export([serialize/4, deserialize/5]).
-export_type([serialization_error/0, decoded_machine/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(
" JSON serialization and deserialization for scamper FSMs.\n"
"\n"
" Users must provide encoder/decoder functions for their custom\n"
" state, context, and event types.\n"
).
-type serialization_error() :: {json_encode_error, binary()} |
{json_decode_error, binary()}.
-type decoded_machine(IJC, IJD, IJE) :: {decoded_machine,
IJC,
IJD,
integer(),
integer(),
list(scamper@history:transition_record(IJC, IJE, IJD))}.
-file("src/scamper/serialization.gleam", 43).
-spec encode_record(
scamper@history:transition_record(IJL, IJM, IJN),
fun((IJL) -> gleam@json:json()),
fun((IJN) -> gleam@json:json()),
fun((IJM) -> gleam@json:json())
) -> gleam@json:json().
encode_record(Record, State_encoder, Context_encoder, Event_encoder) ->
gleam@json:object(
[{<<"from"/utf8>>, State_encoder(erlang:element(2, Record))},
{<<"event"/utf8>>, Event_encoder(erlang:element(3, Record))},
{<<"to"/utf8>>, State_encoder(erlang:element(4, Record))},
{<<"timestamp"/utf8>>, gleam@json:int(erlang:element(5, Record))},
{<<"context_snapshot"/utf8>>,
gleam@json:nullable(erlang:element(6, Record), Context_encoder)}]
).
-file("src/scamper/serialization.gleam", 21).
?DOC(
" Serialize a machine's state to a JSON string.\n"
" User must provide encoder functions for state, context, and event types.\n"
).
-spec serialize(
scamper:machine(IJF, IJG, IJH),
fun((IJF) -> gleam@json:json()),
fun((IJG) -> gleam@json:json()),
fun((IJH) -> gleam@json:json())
) -> binary().
serialize(Machine, State_encoder, Context_encoder, Event_encoder) ->
History_json = begin
_pipe = scamper:history(Machine),
gleam@json:array(
_pipe,
fun(Record) ->
encode_record(
Record,
State_encoder,
Context_encoder,
Event_encoder
)
end
)
end,
_pipe@1 = gleam@json:object(
[{<<"state"/utf8>>, State_encoder(scamper:current_state(Machine))},
{<<"context"/utf8>>,
Context_encoder(scamper:current_context(Machine))},
{<<"created_at"/utf8>>, gleam@json:int(scamper:created_at(Machine))},
{<<"entered_at"/utf8>>, gleam@json:int(scamper:entered_at(Machine))},
{<<"history"/utf8>>, History_json}]
),
gleam@json:to_string(_pipe@1).
-file("src/scamper/serialization.gleam", 110).
-spec build_machine_decoder(
gleam@dynamic@decode:decoder(IKF),
gleam@dynamic@decode:decoder(IKH),
gleam@dynamic@decode:decoder(scamper@history:transition_record(IKF, IKJ, IKH))
) -> gleam@dynamic@decode:decoder(decoded_machine(IKF, IKH, IKJ)).
build_machine_decoder(State_decoder, Context_decoder, Record_decoder) ->
gleam@dynamic@decode:field(
<<"state"/utf8>>,
State_decoder,
fun(State) ->
gleam@dynamic@decode:field(
<<"context"/utf8>>,
Context_decoder,
fun(Context) ->
gleam@dynamic@decode:field(
<<"created_at"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_int/1},
fun(Created_at) ->
gleam@dynamic@decode:field(
<<"entered_at"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_int/1},
fun(Entered_at) ->
gleam@dynamic@decode:field(
<<"history"/utf8>>,
gleam@dynamic@decode:list(
Record_decoder
),
fun(History) ->
gleam@dynamic@decode:success(
{decoded_machine,
State,
Context,
Created_at,
Entered_at,
History}
)
end
)
end
)
end
)
end
)
end
).
-file("src/scamper/serialization.gleam", 129).
-spec build_record_decoder(
gleam@dynamic@decode:decoder(IKS),
gleam@dynamic@decode:decoder(IKU),
gleam@dynamic@decode:decoder(IKW)
) -> gleam@dynamic@decode:decoder(scamper@history:transition_record(IKS, IKW, IKU)).
build_record_decoder(State_decoder, Context_decoder, Event_decoder) ->
gleam@dynamic@decode:field(
<<"from"/utf8>>,
State_decoder,
fun(From) ->
gleam@dynamic@decode:field(
<<"event"/utf8>>,
Event_decoder,
fun(Event) ->
gleam@dynamic@decode:field(
<<"to"/utf8>>,
State_decoder,
fun(To) ->
gleam@dynamic@decode:field(
<<"timestamp"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_int/1},
fun(Timestamp) ->
gleam@dynamic@decode:field(
<<"context_snapshot"/utf8>>,
gleam@dynamic@decode:optional(
Context_decoder
),
fun(Context_snapshot) ->
gleam@dynamic@decode:success(
{transition_record,
From,
Event,
To,
Timestamp,
Context_snapshot}
)
end
)
end
)
end
)
end
)
end
).
-file("src/scamper/serialization.gleam", 63).
?DOC(
" Deserialize a machine from a JSON string and a config.\n"
" User must provide decoder functions for state, context, and event types.\n"
).
-spec deserialize(
binary(),
scamper@config:config(IJR, IJS, IJT),
gleam@dynamic@decode:decoder(IJR),
gleam@dynamic@decode:decoder(IJS),
gleam@dynamic@decode:decoder(IJT)
) -> {ok, scamper:machine(IJR, IJS, IJT)} | {error, serialization_error()}.
deserialize(Data, Config, State_decoder, Context_decoder, Event_decoder) ->
Record_decoder = build_record_decoder(
State_decoder,
Context_decoder,
Event_decoder
),
Machine_decoder = build_machine_decoder(
State_decoder,
Context_decoder,
Record_decoder
),
_pipe = gleam@json:parse(Data, Machine_decoder),
_pipe@1 = gleam@result:map_error(_pipe, fun(Err) -> case Err of
unexpected_end_of_input ->
{json_decode_error, <<"Unexpected end of input"/utf8>>};
{unexpected_byte, B} ->
{json_decode_error, <<"Unexpected byte: "/utf8, B/binary>>};
{unexpected_sequence, S} ->
{json_decode_error,
<<"Unexpected sequence: "/utf8, S/binary>>};
{unable_to_decode, _} ->
{json_decode_error, <<"Unable to decode JSON"/utf8>>}
end end),
gleam@result:map(
_pipe@1,
fun(Decoded) ->
scamper:restore(
Config,
erlang:element(2, Decoded),
erlang:element(3, Decoded),
erlang:element(6, Decoded),
erlang:element(4, Decoded),
erlang:element(5, Decoded)
)
end
).