-module(lattice_presence@state_json).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/lattice_presence/state_json.gleam").
-export([to_json/1, to_json_string/1, decoder/0, from_json/1]).
-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(
" State JSON — serialization and deserialization for the presence CRDT\n"
"\n"
" Provides JSON encoding and decoding of the `State` type for cross-node\n"
" replication.\n"
).
-file("src/lattice_presence/state_json.gleam", 114).
-spec encode_entry(lattice_presence@presence_state:entry()) -> gleam@json:json().
encode_entry(Entry) ->
gleam@json:object(
[{<<"topic"/utf8>>, gleam@json:string(erlang:element(2, Entry))},
{<<"key"/utf8>>, gleam@json:string(erlang:element(3, Entry))},
{<<"pid"/utf8>>, gleam@json:string(erlang:element(4, Entry))},
{<<"meta"/utf8>>, erlang:element(5, Entry)}]
).
-file("src/lattice_presence/state_json.gleam", 97).
-spec encode_tag(lattice_presence@presence_state:tag()) -> gleam@json:json().
encode_tag(Tag) ->
gleam@json:object(
[{<<"replica"/utf8>>, gleam@json:string(erlang:element(2, Tag))},
{<<"clock"/utf8>>, gleam@json:int(erlang:element(3, Tag))}]
).
-file("src/lattice_presence/state_json.gleam", 134).
-spec encode_values(
gleam@dict:dict(lattice_presence@presence_state:tag(), lattice_presence@presence_state:entry())
) -> gleam@json:json().
encode_values(Values) ->
_pipe = Values,
_pipe@1 = maps:to_list(_pipe),
_pipe@2 = gleam@list:map(
_pipe@1,
fun(Kv) ->
gleam@json:object(
[{<<"tag"/utf8>>, encode_tag(erlang:element(1, Kv))},
{<<"entry"/utf8>>, encode_entry(erlang:element(2, Kv))}]
)
end
),
gleam@json:preprocessed_array(_pipe@2).
-file("src/lattice_presence/state_json.gleam", 75).
-spec encode_clouds(gleam@dict:dict(binary(), gleam@set:set(integer()))) -> gleam@json:json().
encode_clouds(Clouds) ->
_pipe = Clouds,
_pipe@1 = maps:to_list(_pipe),
_pipe@2 = gleam@list:map(
_pipe@1,
fun(Kv) ->
{erlang:element(1, Kv),
gleam@json:array(
gleam@set:to_list(erlang:element(2, Kv)),
fun gleam@json:int/1
)}
end
),
gleam@json:object(_pipe@2).
-file("src/lattice_presence/state_json.gleam", 56).
-spec encode_context(gleam@dict:dict(binary(), integer())) -> gleam@json:json().
encode_context(Context) ->
_pipe = Context,
_pipe@1 = maps:to_list(_pipe),
_pipe@2 = gleam@list:map(
_pipe@1,
fun(Kv) ->
{erlang:element(1, Kv), gleam@json:int(erlang:element(2, Kv))}
end
),
gleam@json:object(_pipe@2).
-file("src/lattice_presence/state_json.gleam", 19).
?DOC(" Encode a CRDT State to JSON\n").
-spec to_json(lattice_presence@presence_state:state()) -> gleam@json:json().
to_json(S) ->
{Replica, Context, Clouds, Values} = lattice_presence@presence_state:replicated_parts(
S
),
gleam@json:object(
[{<<"replica"/utf8>>, gleam@json:string(Replica)},
{<<"context"/utf8>>, encode_context(Context)},
{<<"clouds"/utf8>>, encode_clouds(Clouds)},
{<<"values"/utf8>>, encode_values(Values)}]
).
-file("src/lattice_presence/state_json.gleam", 30).
?DOC(" Encode a State to a JSON string\n").
-spec to_json_string(lattice_presence@presence_state:state()) -> binary().
to_json_string(S) ->
_pipe = to_json(S),
gleam@json:to_string(_pipe).
-file("src/lattice_presence/state_json.gleam", 226).
-spec json_value_dict(
list({binary(), gleam@dynamic:dynamic_()}),
list({binary(), gleam@json:json()}),
integer()
) -> gleam@dynamic@decode:decoder(gleam@json:json()).
json_value_dict(Pairs, Acc, Depth) ->
case Pairs of
[] ->
gleam@dynamic@decode:success(gleam@json:object(lists:reverse(Acc)));
[{Key, Value} | Rest] ->
case gleam@dynamic@decode:run(Value, json_value_decoder_at(Depth)) of
{ok, Val} ->
json_value_dict(Rest, [{Key, Val} | Acc], Depth);
{error, _} ->
gleam@dynamic@decode:failure(
gleam@json:null(),
<<"valid JSON value in object"/utf8>>
)
end
end.
-file("src/lattice_presence/state_json.gleam", 211).
-spec json_value_list(
list(gleam@dynamic:dynamic_()),
list(gleam@json:json()),
integer()
) -> gleam@dynamic@decode:decoder(gleam@json:json()).
json_value_list(Items, Acc, Depth) ->
case Items of
[] ->
gleam@dynamic@decode:success(
gleam@json:preprocessed_array(lists:reverse(Acc))
);
[Item | Rest] ->
case gleam@dynamic@decode:run(Item, json_value_decoder_at(Depth)) of
{ok, Val} ->
json_value_list(Rest, [Val | Acc], Depth);
{error, _} ->
gleam@dynamic@decode:failure(
gleam@json:null(),
<<"valid JSON value in array"/utf8>>
)
end
end.
-file("src/lattice_presence/state_json.gleam", 184).
-spec json_value_decoder_within_limit(integer()) -> gleam@dynamic@decode:decoder(gleam@json:json()).
json_value_decoder_within_limit(Depth) ->
gleam@dynamic@decode:one_of(
begin
_pipe = {decoder, fun gleam@dynamic@decode:decode_string/1},
gleam@dynamic@decode:map(_pipe, fun gleam@json:string/1)
end,
[begin
_pipe@1 = {decoder, fun gleam@dynamic@decode:decode_int/1},
gleam@dynamic@decode:map(_pipe@1, fun gleam@json:int/1)
end,
begin
_pipe@2 = {decoder, fun gleam@dynamic@decode:decode_float/1},
gleam@dynamic@decode:map(_pipe@2, fun gleam@json:float/1)
end,
begin
_pipe@3 = {decoder, fun gleam@dynamic@decode:decode_bool/1},
gleam@dynamic@decode:map(_pipe@3, fun gleam@json:bool/1)
end,
begin
_pipe@4 = gleam@dynamic@decode:optional(
{decoder, fun gleam@dynamic@decode:decode_string/1}
),
gleam@dynamic@decode:then(_pipe@4, fun(Opt) -> case Opt of
none ->
gleam@dynamic@decode:success(gleam@json:null());
{some, _} ->
gleam@dynamic@decode:failure(
gleam@json:null(),
<<"null"/utf8>>
)
end end)
end,
begin
_pipe@5 = gleam@dynamic@decode:list(
{decoder, fun gleam@dynamic@decode:decode_dynamic/1}
),
gleam@dynamic@decode:then(
_pipe@5,
fun(Items) -> json_value_list(Items, [], Depth + 1) end
)
end,
begin
_pipe@6 = gleam@dynamic@decode:dict(
{decoder, fun gleam@dynamic@decode:decode_string/1},
{decoder, fun gleam@dynamic@decode:decode_dynamic/1}
),
gleam@dynamic@decode:then(
_pipe@6,
fun(D) ->
Pairs = maps:to_list(D),
json_value_dict(Pairs, [], Depth + 1)
end
)
end]
).
-file("src/lattice_presence/state_json.gleam", 177).
-spec json_value_decoder_at(integer()) -> gleam@dynamic@decode:decoder(gleam@json:json()).
json_value_decoder_at(Depth) ->
case Depth > 64 of
true ->
gleam@dynamic@decode:failure(
gleam@json:null(),
<<"metadata depth within limit"/utf8>>
);
false ->
json_value_decoder_within_limit(Depth)
end.
-file("src/lattice_presence/state_json.gleam", 173).
?DOC(
" Decoder that reconstructs a json.Json value from parsed JSON. Uses\n"
" standard decoder combinators instead of BEAM-specific dynamic.classify\n"
" so the same code works on both the Erlang and JavaScript targets.\n"
).
-spec json_value_decoder() -> gleam@dynamic@decode:decoder(gleam@json:json()).
json_value_decoder() ->
json_value_decoder_at(0).
-file("src/lattice_presence/state_json.gleam", 126).
-spec entry_decoder() -> gleam@dynamic@decode:decoder(lattice_presence@presence_state:entry()).
entry_decoder() ->
gleam@dynamic@decode:field(
<<"topic"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Topic) ->
gleam@dynamic@decode:field(
<<"key"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Key) ->
gleam@dynamic@decode:field(
<<"pid"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Pid) ->
gleam@dynamic@decode:field(
<<"meta"/utf8>>,
json_value_decoder(),
fun(Meta) ->
gleam@dynamic@decode:success(
{entry, Topic, Key, Pid, Meta}
)
end
)
end
)
end
)
end
).
-file("src/lattice_presence/state_json.gleam", 104).
-spec tag_decoder() -> gleam@dynamic@decode:decoder(lattice_presence@presence_state:tag()).
tag_decoder() ->
gleam@dynamic@decode:field(
<<"replica"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Replica) ->
gleam@dynamic@decode:field(
<<"clock"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_int/1},
fun(Clock) -> case Clock > 0 of
true ->
gleam@dynamic@decode:success({tag, Replica, Clock});
false ->
gleam@dynamic@decode:failure(
{tag, Replica, Clock},
<<"positive tag clock"/utf8>>
)
end end
)
end
).
-file("src/lattice_presence/state_json.gleam", 146).
-spec values_decoder() -> gleam@dynamic@decode:decoder(gleam@dict:dict(lattice_presence@presence_state:tag(), lattice_presence@presence_state:entry())).
values_decoder() ->
_pipe = gleam@dynamic@decode:list(
begin
gleam@dynamic@decode:field(
<<"tag"/utf8>>,
tag_decoder(),
fun(Tag) ->
gleam@dynamic@decode:field(
<<"entry"/utf8>>,
entry_decoder(),
fun(Entry) ->
gleam@dynamic@decode:success({Tag, Entry})
end
)
end
)
end
),
gleam@dynamic@decode:map(_pipe, fun maps:from_list/1).
-file("src/lattice_presence/state_json.gleam", 164).
-spec all_cloud_clocks_positive(gleam@dict:dict(binary(), list(integer()))) -> boolean().
all_cloud_clocks_positive(Values) ->
gleam@dict:fold(
Values,
true,
fun(Valid, _, Clocks) ->
Valid andalso gleam@list:all(Clocks, fun(Clock) -> Clock > 0 end)
end
).
-file("src/lattice_presence/state_json.gleam", 82).
-spec clouds_decoder() -> gleam@dynamic@decode:decoder(gleam@dict:dict(binary(), gleam@set:set(integer()))).
clouds_decoder() ->
_pipe = gleam@dynamic@decode:dict(
{decoder, fun gleam@dynamic@decode:decode_string/1},
gleam@dynamic@decode:list(
{decoder, fun gleam@dynamic@decode:decode_int/1}
)
),
gleam@dynamic@decode:then(
_pipe,
fun(D) -> case all_cloud_clocks_positive(D) of
true ->
gleam@dynamic@decode:success(
gleam@dict:map_values(
D,
fun(_, Clocks) -> gleam@set:from_list(Clocks) end
)
);
false ->
gleam@dynamic@decode:failure(
maps:new(),
<<"positive cloud clocks"/utf8>>
)
end end
).
-file("src/lattice_presence/state_json.gleam", 157).
-spec all_dict_values(
gleam@dict:dict(binary(), integer()),
fun((integer()) -> boolean())
) -> boolean().
all_dict_values(Values, Predicate) ->
gleam@dict:fold(
Values,
true,
fun(Valid, _, Value) -> Valid andalso Predicate(Value) end
).
-file("src/lattice_presence/state_json.gleam", 63).
-spec context_decoder() -> gleam@dynamic@decode:decoder(gleam@dict:dict(binary(), integer())).
context_decoder() ->
_pipe = gleam@dynamic@decode:dict(
{decoder, fun gleam@dynamic@decode:decode_string/1},
{decoder, fun gleam@dynamic@decode:decode_int/1}
),
gleam@dynamic@decode:then(
_pipe,
fun(Context) ->
case all_dict_values(Context, fun(Clock) -> Clock >= 0 end) of
true ->
gleam@dynamic@decode:success(Context);
false ->
gleam@dynamic@decode:failure(
Context,
<<"non-negative context clocks"/utf8>>
)
end
end
).
-file("src/lattice_presence/state_json.gleam", 41).
?DOC(
" Decoder for the CRDT State type. Used by `from_json` and\n"
" available for embedding in larger decoders (e.g. sync envelope parsing).\n"
).
-spec decoder() -> gleam@dynamic@decode:decoder(lattice_presence@presence_state:state()).
decoder() ->
gleam@dynamic@decode:field(
<<"replica"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Replica) ->
gleam@dynamic@decode:field(
<<"context"/utf8>>,
context_decoder(),
fun(Context) ->
gleam@dynamic@decode:field(
<<"clouds"/utf8>>,
clouds_decoder(),
fun(Clouds) ->
gleam@dynamic@decode:field(
<<"values"/utf8>>,
values_decoder(),
fun(Values) ->
gleam@dynamic@decode:success(
lattice_presence@presence_state:from_replicated_parts(
Replica,
Context,
Clouds,
Values
)
)
end
)
end
)
end
)
end
).
-file("src/lattice_presence/state_json.gleam", 35).
?DOC(" Decode a JSON string into a State\n").
-spec from_json(binary()) -> {ok, lattice_presence@presence_state:state()} |
{error, gleam@json:decode_error()}.
from_json(Json_string) ->
gleam@json:parse(Json_string, decoder()).