-module(lightspeed@form). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/lightspeed/form.gleam"). -export([empty/0, from_entries/1, parse_payload/1, fields/1, to_entries/1, value/2, values/2, require/2, int/2, bool/2, checked/2, put/3, error_to_string/1]). -export_type([field/0, form_data/0, form_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(" Form binding helpers for event payloads and typed form values.\n"). -type field() :: {field, binary(), binary()}. -type form_data() :: {form_data, list(field())}. -type form_error() :: {missing_field, binary()} | {invalid_integer, binary(), binary()} | {invalid_boolean, binary(), binary()}. -file("src/lightspeed/form.gleam", 26). ?DOC(" Empty form data.\n"). -spec empty() -> form_data(). empty() -> {form_data, []}. -file("src/lightspeed/form.gleam", 130). -spec from_entries_loop(list({binary(), binary()}), list(field())) -> list(field()). from_entries_loop(Entries, Fields_rev) -> case Entries of [] -> lists:reverse(Fields_rev); [{Name, Value} | Rest] -> from_entries_loop(Rest, [{field, Name, Value} | Fields_rev]) end. -file("src/lightspeed/form.gleam", 31). ?DOC(" Build form data from key/value entries.\n"). -spec from_entries(list({binary(), binary()})) -> form_data(). from_entries(Entries) -> {form_data, from_entries_loop(Entries, [])}. -file("src/lightspeed/form.gleam", 183). -spec normalize_component(binary()) -> binary(). normalize_component(Value) -> _pipe = Value, gleam@string:replace(_pipe, <<"+"/utf8>>, <<" "/utf8>>). -file("src/lightspeed/form.gleam", 175). -spec join_with_equals(list(binary())) -> binary(). join_with_equals(Parts) -> case Parts of [] -> <<""/utf8>>; [Part] -> Part; [Part@1 | Rest] -> <<< {binary(), binary()}. split_pair(Pair) -> case gleam@string:split(Pair, <<"="/utf8>>) of [] -> {<<""/utf8>>, <<""/utf8>>}; [Name] -> {Name, <<""/utf8>>}; [Name@1, Value] -> {Name@1, Value}; [Name@2, Value@1 | Rest] -> {Name@2, <<< list(HTW). concat(Left, Right) -> case Left of [] -> Right; [Entry | Rest] -> [Entry | concat(Rest, Right)] end. -file("src/lightspeed/form.gleam", 141). -spec parse_pairs(list(binary()), list(field()), list(field())) -> list(field()). parse_pairs(Pairs, Fields_rev, Buffer) -> case Pairs of [] -> lists:reverse(concat(Buffer, Fields_rev)); [Pair | Rest] -> case split_pair(Pair) of {Name, Value} -> parse_pairs( Rest, [{field, normalize_component(Name), normalize_component(Value)} | Fields_rev], Buffer ) end end. -file("src/lightspeed/form.gleam", 39). ?DOC( " Parse a URL-form-encoded style payload (`name=value&next=...`).\n" "\n" " This parser keeps values as plain strings and does not perform percent\n" " decoding. It is deterministic and suitable for typed server decoders.\n" ). -spec parse_payload(binary()) -> form_data(). parse_payload(Payload) -> case Payload of <<""/utf8>> -> empty(); _ -> _pipe = Payload, _pipe@1 = gleam@string:split(_pipe, <<"&"/utf8>>), _pipe@2 = parse_pairs(_pipe@1, [], []), {form_data, _pipe@2} end. -file("src/lightspeed/form.gleam", 51). ?DOC(" Return all fields.\n"). -spec fields(form_data()) -> list(field()). fields(Form) -> erlang:element(2, Form). -file("src/lightspeed/form.gleam", 188). -spec to_entries_loop(list(field()), list({binary(), binary()})) -> list({binary(), binary()}). to_entries_loop(Fields, Entries_rev) -> case Fields of [] -> lists:reverse(Entries_rev); [{field, Name, Value} | Rest] -> to_entries_loop(Rest, [{Name, Value} | Entries_rev]) end. -file("src/lightspeed/form.gleam", 56). ?DOC(" Convert form data back into entries.\n"). -spec to_entries(form_data()) -> list({binary(), binary()}). to_entries(Form) -> to_entries_loop(erlang:element(2, Form), []). -file("src/lightspeed/form.gleam", 199). -spec find_first(list(field()), binary()) -> gleam@option:option(binary()). find_first(Fields, Name) -> case Fields of [] -> none; [{field, Field_name, Field_value} | Rest] -> case Field_name =:= Name of true -> {some, Field_value}; false -> find_first(Rest, Name) end end. -file("src/lightspeed/form.gleam", 61). ?DOC(" Return first value for a field.\n"). -spec value(form_data(), binary()) -> gleam@option:option(binary()). value(Form, Name) -> find_first(erlang:element(2, Form), Name). -file("src/lightspeed/form.gleam", 210). -spec find_all(list(field()), binary(), list(binary())) -> list(binary()). find_all(Fields, Name, Values_rev) -> case Fields of [] -> lists:reverse(Values_rev); [{field, Field_name, Field_value} | Rest] -> case Field_name =:= Name of true -> find_all(Rest, Name, [Field_value | Values_rev]); false -> find_all(Rest, Name, Values_rev) end end. -file("src/lightspeed/form.gleam", 66). ?DOC(" Return all values for a field.\n"). -spec values(form_data(), binary()) -> list(binary()). values(Form, Name) -> find_all(erlang:element(2, Form), Name, []). -file("src/lightspeed/form.gleam", 71). ?DOC(" Return a required field or an explicit missing-field error.\n"). -spec require(form_data(), binary()) -> {ok, binary()} | {error, form_error()}. require(Form, Name) -> case value(Form, Name) of {some, Field_value} -> {ok, Field_value}; none -> {error, {missing_field, Name}} end. -file("src/lightspeed/form.gleam", 79). ?DOC(" Parse a required integer field.\n"). -spec int(form_data(), binary()) -> {ok, integer()} | {error, form_error()}. int(Form, Name) -> case require(Form, Name) of {error, Error} -> {error, Error}; {ok, Field_value} -> case gleam_stdlib:parse_int(Field_value) of {ok, Parsed} -> {ok, Parsed}; {error, _} -> {error, {invalid_integer, Name, Field_value}} end end. -file("src/lightspeed/form.gleam", 91). ?DOC(" Parse a required boolean field.\n"). -spec bool(form_data(), binary()) -> {ok, boolean()} | {error, form_error()}. bool(Form, Name) -> case require(Form, Name) of {error, Error} -> {error, Error}; {ok, Field_value} -> case Field_value of <<"true"/utf8>> -> {ok, true}; <<"false"/utf8>> -> {ok, false}; <<"1"/utf8>> -> {ok, true}; <<"0"/utf8>> -> {ok, false}; <<"on"/utf8>> -> {ok, true}; <<"off"/utf8>> -> {ok, false}; _ -> {error, {invalid_boolean, Name, Field_value}} end end. -file("src/lightspeed/form.gleam", 108). ?DOC(" Presence helper for checkbox-style fields.\n"). -spec checked(form_data(), binary()) -> boolean(). checked(Form, Name) -> case value(Form, Name) of {some, _} -> true; none -> false end. -file("src/lightspeed/form.gleam", 225). -spec remove_name(list(field()), binary(), list(field())) -> list(field()). remove_name(Fields, Name, Kept_rev) -> case Fields of [] -> lists:reverse(Kept_rev); [Field | Rest] -> case erlang:element(2, Field) =:= Name of true -> remove_name(Rest, Name, Kept_rev); false -> remove_name(Rest, Name, [Field | Kept_rev]) end end. -file("src/lightspeed/form.gleam", 116). ?DOC(" Upsert one form field value.\n"). -spec put(form_data(), binary(), binary()) -> form_data(). put(Form, Name, Value) -> Remaining = remove_name(erlang:element(2, Form), Name, []), {form_data, [{field, Name, Value} | Remaining]}. -file("src/lightspeed/form.gleam", 122). ?DOC(" Render stable error string for logs or telemetry.\n"). -spec error_to_string(form_error()) -> binary(). error_to_string(Error) -> case Error of {missing_field, Name} -> <<"missing_field:"/utf8, Name/binary>>; {invalid_integer, Name@1, Value} -> <<<<<<"invalid_integer:"/utf8, Name@1/binary>>/binary, ":"/utf8>>/binary, Value/binary>>; {invalid_boolean, Name@2, Value@1} -> <<<<<<"invalid_boolean:"/utf8, Name@2/binary>>/binary, ":"/utf8>>/binary, Value@1/binary>> end.