Current section
Files
Jump to
Current section
Files
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, fold/5, format_transport_error/1, format_failure/2]).
-export_type([remote_data/2, rpc_outcome/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(
" 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\n"
" asynchronously. The view pattern matches directly on the state -\n"
" impossible to show stale data while loading or forget to handle errors.\n"
"\n"
" Generated RPC stubs build `RpcData` directly: the per-endpoint\n"
" `decode_response_<fn>` FFI returns an `RpcData(payload, domain)` that\n"
" the page stores in its model. `NotAsked` and `Loading` are page-lifecycle\n"
" states the page sets itself in `init` and `update`. `Failure` carries\n"
" either a `TransportError(RpcError)` (framework / wire-level) or a typed\n"
" `DomainError(domain)` from the handler's own error type.\n"
).
-type remote_data(AFEJ, AFEK) :: not_asked |
loading |
{failure, AFEK} |
{success, AFEJ}.
-type rpc_outcome(AFEL) :: {transport_error, libero@error:rpc_error()} |
{domain_error, AFEL}.
-file("src/libero/remote_data.gleam", 43).
?DOC(" Apply a function to the success value.\n").
-spec map(remote_data(AFER, AFES), fun((AFER) -> AFEV)) -> remote_data(AFEV, AFES).
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", 56).
?DOC(" Apply a function to the error value.\n").
-spec map_error(remote_data(AFEY, AFEZ), fun((AFEZ) -> AFFC)) -> remote_data(AFEY, AFFC).
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", 69).
?DOC(" Extract the success value, or return a default.\n").
-spec unwrap(remote_data(AFFF, any()), AFFF) -> AFFF.
unwrap(Data, Default) ->
case Data of
{success, Value} ->
Value;
_ ->
Default
end.
-file("src/libero/remote_data.gleam", 77).
?DOC(" Convert to Option - Some for Success, None for everything else.\n").
-spec to_option(remote_data(AFFJ, any())) -> gleam@option:option(AFFJ).
to_option(Data) ->
case Data of
{success, Value} ->
{some, Value};
_ ->
none
end.
-file("src/libero/remote_data.gleam", 87).
?DOC(
" Reduce all four states into a single value. Each case provides its\n"
" own callback so the caller can return whatever shape they need\n"
" (e.g. an `Element(msg)` for a Lustre view).\n"
).
-spec fold(
remote_data(AFFO, AFFP),
fun(() -> AFFS),
fun(() -> AFFS),
fun((AFFP) -> AFFS),
fun((AFFO) -> AFFS)
) -> AFFS.
fold(Data, On_not_asked, On_loading, On_failure, On_success) ->
case Data of
not_asked ->
On_not_asked();
loading ->
On_loading();
{failure, Error} ->
On_failure(Error);
{success, Value} ->
On_success(Value)
end.
-file("src/libero/remote_data.gleam", 105).
?DOC(
" Default formatter for framework-level transport errors. Useful when\n"
" the caller wants a single string for `RpcError` values rather than\n"
" pattern-matching each variant.\n"
).
-spec format_transport_error(libero@error:rpc_error()) -> binary().
format_transport_error(Err) ->
case Err of
{internal_error, _, Message} ->
Message;
{unknown_function, Name} ->
<<"Unknown RPC: "/utf8, Name/binary>>;
malformed_request ->
<<"Malformed request"/utf8>>
end.
-file("src/libero/remote_data.gleam", 137).
?DOC(
" Render an `RpcOutcome` as a single user-facing string. The caller\n"
" supplies a formatter for their domain error type; transport errors\n"
" are routed through `format_transport_error`.\n"
"\n"
" Use this anywhere two arms differ only by which formatter runs. In\n"
" views:\n"
"\n"
" ```\n"
" Failure(outcome) -> render(format_failure(outcome, format_my_error))\n"
" ```\n"
"\n"
" In an update reducer that surfaces a flash message:\n"
"\n"
" ```\n"
" LoadResult(Failure(outcome)) -> #(\n"
" model,\n"
" effect.none(),\n"
" Some(#(Danger, format_failure(outcome, format_my_error))),\n"
" )\n"
" ```\n"
"\n"
" The match becomes exhaustive over `RpcOutcome`, so no\n"
" `Failure(_)` catch-all is needed and the per-tier `DomainError(err)` /\n"
" `TransportError(rpc_err)` arms collapse into one.\n"
).
-spec format_failure(rpc_outcome(AFFT), fun((AFFT) -> binary())) -> binary().
format_failure(Outcome, Format_domain) ->
case Outcome of
{transport_error, Err} ->
format_transport_error(Err);
{domain_error, Err@1} ->
Format_domain(Err@1)
end.