Current section

Files

Jump to
libero src libero@remote_data.erl
Raw

src/libero@remote_data.erl

-module(libero@remote_data).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/libero/remote_data.gleam").
-export([map/2, map_error/2, unwrap/2, to_option/1, is_success/1, is_loading/1, to_remote/2, to_result/2]).
-export_type([remote_data/2, rpc_failure/0]).
-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(
" Typed states for async data loading.\n"
" Inspired by Elm's RemoteData package.\n"
"\n"
" Use instead of Bool flags + Option fields for data that loads asynchronously.\n"
" The view pattern matches directly on the state - impossible to show stale\n"
" data while loading or forget to handle errors.\n"
"\n"
" `to_remote` is the bridge from a libero RPC response (a Dynamic value\n"
" shipped from the server) to a `RemoteData` value the page stores in\n"
" its model. It collapses three wire outcomes - domain success, domain\n"
" failure, framework failure - into the two post-response states\n"
" (`Success` or `Failure`). The `NotAsked` and `Loading` states are\n"
" page-lifecycle concerns the page sets itself in `init` and `update`.\n"
).
-type remote_data(WQI, WQJ) :: not_asked |
loading |
{failure, WQJ} |
{success, WQI}.
-type rpc_failure() :: {domain_failure, binary()} |
{framework_failure, binary()}.
-file("src/libero/remote_data.gleam", 35).
?DOC(" Apply a function to the success value.\n").
-spec map(remote_data(WQK, WQL), fun((WQK) -> WQO)) -> remote_data(WQO, WQL).
map(Data, Transform) ->
case Data of
not_asked ->
not_asked;
loading ->
loading;
{failure, Error} ->
{failure, Error};
{success, Value} ->
{success, Transform(Value)}
end.
-file("src/libero/remote_data.gleam", 48).
?DOC(" Apply a function to the error value.\n").
-spec map_error(remote_data(WQR, WQS), fun((WQS) -> WQV)) -> remote_data(WQR, WQV).
map_error(Data, Transform) ->
case Data of
not_asked ->
not_asked;
loading ->
loading;
{failure, Error} ->
{failure, Transform(Error)};
{success, Value} ->
{success, Value}
end.
-file("src/libero/remote_data.gleam", 61).
?DOC(" Extract the success value, or return a default.\n").
-spec unwrap(remote_data(WQY, any()), WQY) -> WQY.
unwrap(Data, Default) ->
case Data of
{success, Value} ->
Value;
_ ->
Default
end.
-file("src/libero/remote_data.gleam", 69).
?DOC(" Convert to Option - Some for Success, None for everything else.\n").
-spec to_option(remote_data(WRC, any())) -> gleam@option:option(WRC).
to_option(Data) ->
case Data of
{success, Value} ->
{some, Value};
_ ->
none
end.
-file("src/libero/remote_data.gleam", 77).
?DOC(" Check if the data is loaded successfully.\n").
-spec is_success(remote_data(any(), any())) -> boolean().
is_success(Data) ->
case Data of
{success, _} ->
true;
_ ->
false
end.
-file("src/libero/remote_data.gleam", 85).
?DOC(" Check if the data is currently loading.\n").
-spec is_loading(remote_data(any(), any())) -> boolean().
is_loading(Data) ->
case Data of
loading ->
true;
_ ->
false
end.
-file("src/libero/remote_data.gleam", 159).
?DOC(
" Default formatter for framework-level RPC errors.\n"
" Domain errors travel inside `Ok(Error(domain))` and are formatted\n"
" by the caller's `format_domain` function.\n"
).
-spec format_rpc_error(libero@error:rpc_error(any())) -> rpc_failure().
format_rpc_error(Err) ->
case Err of
{app_error, _} ->
{framework_failure, <<"Server application error"/utf8>>};
{internal_error, _, Message} ->
{framework_failure, Message};
{unknown_function, Name} ->
{framework_failure, <<"Unknown RPC: "/utf8, Name/binary>>};
malformed_request ->
{framework_failure, <<"Malformed request"/utf8>>}
end.
-file("src/libero/remote_data.gleam", 103).
?DOC(
" Convert a libero RPC response (Dynamic) into a `RemoteData` value.\n"
"\n"
" The server-side dispatch ships the full MsgFromServer envelope so the\n"
" wire response is `Result(MsgFromServer.Variant(Result(payload, domain_err)), RpcError(app_err))`.\n"
" This helper peels the MsgFromServer wrapper and collapses the result\n"
" into the two post-response states.\n"
"\n"
" The `format_domain` callback formats domain errors (which the page\n"
" owns and pattern-matches on its specific error type). Framework\n"
" errors (internal, unknown function, malformed request, server\n"
" AppError) are formatted by libero's default formatter.\n"
).
-spec to_remote(gleam@dynamic:dynamic_(), fun((any()) -> binary())) -> remote_data(any(), rpc_failure()).
to_remote(Raw, Format_domain) ->
Outer = libero_ffi:identity(Raw),
case Outer of
{error, Rpc_err} ->
{failure, format_rpc_error(Rpc_err)};
{ok, Wrapped} ->
Inner = libero_ffi:peel_msg_wrapper(Wrapped),
Result = libero_ffi:identity(Inner),
case Result of
{ok, Payload} ->
{success, Payload};
{error, Domain_err} ->
{failure, {domain_failure, Format_domain(Domain_err)}}
end
end.
-file("src/libero/remote_data.gleam", 142).
?DOC(
" Adapter for action responses that the page wants as a flat `Result`\n"
" rather than `RemoteData`. Useful when the response only feeds a flash\n"
" message or triggers a redirect - the page never needs to render\n"
" `NotAsked`/`Loading` states for the action result itself.\n"
).
-spec to_result(gleam@dynamic:dynamic_(), fun((any()) -> binary())) -> {ok,
any()} |
{error, rpc_failure()}.
to_result(Raw, Format_domain) ->
case to_remote(Raw, Format_domain) of
{success, Payload} ->
{ok, Payload};
{failure, Err} ->
{error, Err};
not_asked ->
{error,
{framework_failure,
<<"Unexpected response state: to_result called on a non-response"/utf8>>}};
loading ->
{error,
{framework_failure,
<<"Unexpected response state: to_result called on a non-response"/utf8>>}}
end.