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(NEP, NEQ) :: not_asked |
loading |
{failure, NEQ} |
{success, NEP}.
-type rpc_outcome(NER) :: {transport_error, libero@error:rpc_error()} |
{domain_error, NER}.
-file("src/libero/remote_data.gleam", 43).
?DOC(" Apply a function to the success value.\n").
-spec map(remote_data(NEX, NEY), fun((NEX) -> NFB)) -> remote_data(NFB, NEY).
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", 69).
?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"
" When both inputs are `Failure`, the left-hand error is returned.\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(NFE, NFF), remote_data(NFI, NFF), fun((NFE, NFI) -> NFL)) -> remote_data(NFL, NFF).
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", 91).
?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(NFO, NFP), fun((NFO) -> remote_data(NFS, NFP))) -> remote_data(NFS, NFP).
'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", 104).
?DOC(" Apply a function to the error value.\n").
-spec map_error(remote_data(NFX, NFY), fun((NFY) -> NGB)) -> remote_data(NFX, NGB).
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", 117).
?DOC(" Extract the success value, or return a default.\n").
-spec unwrap(remote_data(NGE, any()), NGE) -> NGE.
unwrap(Data, Default) ->
case Data of
{success, Value} ->
Value;
_ ->
Default
end.
-file("src/libero/remote_data.gleam", 125).
?DOC(" Convert to Option - Some for Success, None for everything else.\n").
-spec to_option(remote_data(NGI, any())) -> gleam@option:option(NGI).
to_option(Data) ->
case Data of
{success, Value} ->
{some, Value};
_ ->
none
end.
-file("src/libero/remote_data.gleam", 135).
?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(NGN, NGO),
fun(() -> NGR),
fun(() -> NGR),
fun((NGO) -> NGR),
fun((NGN) -> NGR)
) -> NGR.
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", 153).
?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", 185).
?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(NGS), fun((NGS) -> 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.