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, map2/3, 'try'/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(AFEI, AFEJ) :: not_asked |
loading |
{failure, AFEJ} |
{success, AFEI}.
-type rpc_outcome(AFEK) :: {transport_error, libero@error:rpc_error()} |
{domain_error, AFEK}.
-file("src/libero/remote_data.gleam", 43).
?DOC(" Apply a function to the success value.\n").
-spec map(remote_data(AFEQ, AFER), fun((AFEQ) -> AFEU)) -> remote_data(AFEU, AFER).
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", 68).
?DOC(
" Combine two independent `RemoteData` values with a function. Returns\n"
" `Success` only when both inputs are `Success`. Short-circuits on the\n"
" first `Failure` found (in either position), or `Loading` if either is\n"
" `Loading` and neither has failed.\n"
"\n"
" Useful in `init` to batch parallel RPC calls into a single state\n"
" transition:\n"
"\n"
" ```\n"
" let result = remote_data.map2(config_rd, items_rd, fn(c, i) {\n"
" #(c, i)\n"
" })\n"
" ```\n"
).
-spec map2(
remote_data(AFEX, AFEY),
remote_data(AFFB, AFEY),
fun((AFEX, AFFB) -> AFFE)
) -> remote_data(AFFE, AFEY).
map2(A, B, Combine) ->
case {A, B} of
{{success, A@1}, {success, B@1}} ->
{success, Combine(A@1, B@1)};
{{failure, E}, _} ->
{failure, E};
{_, {failure, E@1}} ->
{failure, E@1};
{loading, _} ->
loading;
{_, loading} ->
loading;
{_, _} ->
not_asked
end.
-file("src/libero/remote_data.gleam", 90).
?DOC(
" Monadic bind for chaining dependent async work. When the data is\n"
" `Success`, apply `f` to the value and return its result. Otherwise\n"
" propagate the current state unchanged.\n"
"\n"
" RPC calls that depend on a prior result usually live in separate\n"
" `update` arms rather than inside `init`, so this is less common than\n"
" `map` or `map2` — but available when callers need it.\n"
).
-spec 'try'(remote_data(AFFH, AFFI), fun((AFFH) -> remote_data(AFFL, AFFI))) -> remote_data(AFFL, AFFI).
'try'(Data, F) ->
case Data of
{success, Value} ->
F(Value);
not_asked ->
not_asked;
loading ->
loading;
{failure, Error} ->
{failure, Error}
end.
-file("src/libero/remote_data.gleam", 103).
?DOC(" Apply a function to the error value.\n").
-spec map_error(remote_data(AFFQ, AFFR), fun((AFFR) -> AFFU)) -> remote_data(AFFQ, AFFU).
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", 116).
?DOC(" Extract the success value, or return a default.\n").
-spec unwrap(remote_data(AFFX, any()), AFFX) -> AFFX.
unwrap(Data, Default) ->
case Data of
{success, Value} ->
Value;
_ ->
Default
end.
-file("src/libero/remote_data.gleam", 124).
?DOC(" Convert to Option - Some for Success, None for everything else.\n").
-spec to_option(remote_data(AFGB, any())) -> gleam@option:option(AFGB).
to_option(Data) ->
case Data of
{success, Value} ->
{some, Value};
_ ->
none
end.
-file("src/libero/remote_data.gleam", 134).
?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(AFGG, AFGH),
fun(() -> AFGK),
fun(() -> AFGK),
fun((AFGH) -> AFGK),
fun((AFGG) -> AFGK)
) -> AFGK.
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", 152).
?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", 184).
?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(AFGL), fun((AFGL) -> 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.