Current section

Files

Jump to
ssevents src ssevents@limit.erl
Raw

src/ssevents@limit.erl

-module(ssevents@limit).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/ssevents/limit.gleam").
-export([new/4, max_line_bytes/1, max_event_bytes/1, max_data_lines/1, max_retry_value/1, strict_retry_cap/1, with_strict_retry_cap/2, max_event_size/1, with_max_event_size/2, new_checked/4, default/0]).
-export_type([limits/0, limit_config_error/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(
" Parser and decoder safety limits.\n"
"\n"
" These values bound memory growth for the incremental decoder. The\n"
" `new` constructor rejects nonsensical values with a panic so callers\n"
" do not silently run with ineffective limits. For dynamic input\n"
" (config / env / framework), use `new_checked` and surface\n"
" `LimitConfigError.NonPositiveLimit` through your normal result\n"
" chain.\n"
).
-opaque limits() :: {limits,
integer(),
integer(),
integer(),
integer(),
boolean()}.
-type limit_config_error() :: {non_positive_limit, binary(), integer()}.
-file("src/ssevents/limit.gleam", 56).
-spec new(integer(), integer(), integer(), integer()) -> limits().
new(Max_line_bytes, Max_event_bytes, Max_data_lines, Max_retry_value) ->
case Max_line_bytes < 1 of
true ->
erlang:error(#{gleam_error => panic,
message => <<"max_line_bytes must be >= 1"/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"ssevents/limit"/utf8>>,
function => <<"new"/utf8>>,
line => 63});
false ->
nil
end,
case Max_event_bytes < 1 of
true ->
erlang:error(#{gleam_error => panic,
message => <<"max_event_bytes must be >= 1"/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"ssevents/limit"/utf8>>,
function => <<"new"/utf8>>,
line => 67});
false ->
nil
end,
case Max_data_lines < 1 of
true ->
erlang:error(#{gleam_error => panic,
message => <<"max_data_lines must be >= 1"/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"ssevents/limit"/utf8>>,
function => <<"new"/utf8>>,
line => 71});
false ->
nil
end,
case Max_retry_value < 0 of
true ->
erlang:error(#{gleam_error => panic,
message => <<"max_retry_value must be >= 0"/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"ssevents/limit"/utf8>>,
function => <<"new"/utf8>>,
line => 75});
false ->
nil
end,
{limits,
Max_line_bytes,
Max_event_bytes,
Max_data_lines,
Max_retry_value,
false}.
-file("src/ssevents/limit.gleam", 87).
-spec max_line_bytes(limits()) -> integer().
max_line_bytes(Limits) ->
{limits, Max_line_bytes, _, _, _, _} = Limits,
Max_line_bytes.
-file("src/ssevents/limit.gleam", 92).
-spec max_event_bytes(limits()) -> integer().
max_event_bytes(Limits) ->
{limits, _, Max_event_bytes, _, _, _} = Limits,
Max_event_bytes.
-file("src/ssevents/limit.gleam", 97).
-spec max_data_lines(limits()) -> integer().
max_data_lines(Limits) ->
{limits, _, _, Max_data_lines, _, _} = Limits,
Max_data_lines.
-file("src/ssevents/limit.gleam", 102).
-spec max_retry_value(limits()) -> integer().
max_retry_value(Limits) ->
{limits, _, _, _, Max_retry_value, _} = Limits,
Max_retry_value.
-file("src/ssevents/limit.gleam", 113).
?DOC(
" Read the `strict_retry_cap` flag from a `Limits` value.\n"
"\n"
" When `True`, the decoder errors with `InvalidRetry(_)` on `retry:`\n"
" values above `max_retry_value`; when `False` (the default), it\n"
" silently drops the offending field to `None` and the surrounding\n"
" event still dispatches. See `with_strict_retry_cap/2`. (#95)\n"
).
-spec strict_retry_cap(limits()) -> boolean().
strict_retry_cap(Limits) ->
{limits, _, _, _, _, Strict_retry_cap} = Limits,
Strict_retry_cap.
-file("src/ssevents/limit.gleam", 126).
?DOC(
" Toggle the decoder's `retry:` cap-overrun posture.\n"
"\n"
" Defaults to `False` (lenient): a `retry:` value whose integer form\n"
" exceeds `max_retry_value` is silently dropped to `None` so the\n"
" surrounding event still dispatches, mirroring the encoder's\n"
" `retry_clamp/2` and matching WHATWG SSE's lenient parser thesis.\n"
" Opt into `True` when downstream code needs to detect adversarial\n"
" retry values explicitly. (#95)\n"
).
-spec with_strict_retry_cap(limits(), boolean()) -> limits().
with_strict_retry_cap(Limits, Strict) ->
{limits,
erlang:element(2, Limits),
erlang:element(3, Limits),
erlang:element(4, Limits),
erlang:element(5, Limits),
Strict}.
-file("src/ssevents/limit.gleam", 135).
?DOC(
" Alias of `max_event_bytes/1`. Reads the per-event byte cap the\n"
" decoder uses to reject oversize events with\n"
" `Error(EventTooLarge(_))`. Provided so callers can stay in the\n"
" `with_max_event_size` / `max_event_size` spelling pair when\n"
" raising the limit explicitly. (#96)\n"
).
-spec max_event_size(limits()) -> integer().
max_event_size(Limits) ->
max_event_bytes(Limits).
-file("src/ssevents/limit.gleam", 161).
?DOC(
" Override the per-event byte cap on a `Limits` value.\n"
"\n"
" `ssevents.decode/1` runs with `default_max_event_bytes` (65_536) and\n"
" fails the whole stream with `Error(EventTooLarge(65_536))` for any\n"
" event above that cap — symmetric with `max_line_bytes` /\n"
" `max_data_lines`, but asymmetric with the encoder side which never\n"
" rejects an event for sheer size. A caller that knowingly emits a\n"
" 100 KB event and pipes it back through the decoder needs to raise\n"
" the cap explicitly:\n"
"\n"
" ```gleam\n"
" let limits =\n"
" ssevents.default_limits()\n"
" |> ssevents.with_max_event_size(200_000)\n"
" ssevents.decode_with_limits(wire, limits: limits)\n"
" ```\n"
"\n"
" The default `decode/1` deliberately keeps the 65_536-byte ceiling\n"
" as a memory-bound safety net for untrusted input; reach for\n"
" `decode_with_limits` + `with_max_event_size` when the input is\n"
" trusted and known to be larger. Panics on `bytes < 1` to mirror\n"
" `new/4`'s posture. (#96)\n"
).
-spec with_max_event_size(limits(), integer()) -> limits().
with_max_event_size(Limits, Bytes) ->
case Bytes < 1 of
true ->
erlang:error(#{gleam_error => panic,
message => <<"max_event_size must be >= 1"/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"ssevents/limit"/utf8>>,
function => <<"with_max_event_size"/utf8>>,
line => 163});
false ->
nil
end,
{limits,
erlang:element(2, Limits),
Bytes,
erlang:element(4, Limits),
erlang:element(5, Limits),
erlang:element(6, Limits)}.
-file("src/ssevents/limit.gleam", 196).
-spec check_min(binary(), integer(), integer()) -> {ok, nil} |
{error, limit_config_error()}.
check_min(Field, Value, Minimum) ->
gleam@bool:guard(
Value < Minimum,
{error, {non_positive_limit, Field, Value}},
fun() -> {ok, nil} end
).
-file("src/ssevents/limit.gleam", 177).
?DOC(
" Like `new`, but returns the argument-validation failure as a\n"
" `Result` instead of panicking. Use this when limit values come\n"
" from configuration, environment variables, or other dynamic\n"
" sources where a malformed value is a recoverable runtime\n"
" condition rather than a programmer error.\n"
"\n"
" On success the returned `Limits` is identical to what `new`\n"
" would return for the same arguments.\n"
).
-spec new_checked(integer(), integer(), integer(), integer()) -> {ok, limits()} |
{error, limit_config_error()}.
new_checked(Max_line_bytes, Max_event_bytes, Max_data_lines, Max_retry_value) ->
gleam@result:'try'(
check_min(<<"max_line_bytes"/utf8>>, Max_line_bytes, 1),
fun(_) ->
gleam@result:'try'(
check_min(<<"max_event_bytes"/utf8>>, Max_event_bytes, 1),
fun(_) ->
gleam@result:'try'(
check_min(<<"max_data_lines"/utf8>>, Max_data_lines, 1),
fun(_) ->
gleam@result:'try'(
check_min(
<<"max_retry_value"/utf8>>,
Max_retry_value,
0
),
fun(_) ->
{ok,
{limits,
Max_line_bytes,
Max_event_bytes,
Max_data_lines,
Max_retry_value,
false}}
end
)
end
)
end
)
end
).
-file("src/ssevents/limit.gleam", 46).
-spec default() -> limits().
default() ->
{limits, 8192, 65536, 1024, 86400000, false}.