Current section

Files

Jump to
multipartkit src multipartkit@encoder.erl
Raw

src/multipartkit@encoder.erl

-module(multipartkit@encoder).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/multipartkit/encoder.gleam").
-export([encode/2, encode_stream/2, encode_form/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.
-file("src/multipartkit/encoder.gleam", 39).
-spec build_header_block(list({binary(), binary()})) -> bitstring().
build_header_block(Headers) ->
gleam@list:fold(
Headers,
<<>>,
fun(Acc, Entry) ->
Line = gleam_stdlib:bit_array_concat(
[gleam_stdlib:identity(erlang:element(1, Entry)),
<<": "/utf8>>,
gleam_stdlib:identity(erlang:element(2, Entry)),
<<"\r\n"/utf8>>]
),
gleam@bit_array:append(Acc, Line)
end
).
-file("src/multipartkit/encoder.gleam", 17).
?DOC(
" Encode parts using the supplied boundary.\n"
"\n"
" The boundary must satisfy the RFC 2046 grammar; otherwise the resulting\n"
" message will be invalid. To keep this function pure and total, the encoder\n"
" does not validate the boundary.\n"
).
-spec encode(binary(), list(multipartkit@part:part())) -> bitstring().
encode(Boundary, Parts) ->
Dash = <<"--"/utf8>>,
Crlf = <<"\r\n"/utf8>>,
Boundary_bytes = gleam_stdlib:identity(Boundary),
Initial = <<>>,
Body = gleam@list:fold(
Parts,
Initial,
fun(Acc, The_part) ->
Header_block = build_header_block(erlang:element(2, The_part)),
gleam_stdlib:bit_array_concat(
[Acc,
Dash,
Boundary_bytes,
Crlf,
Header_block,
Crlf,
erlang:element(6, The_part),
Crlf]
)
end
),
gleam_stdlib:bit_array_concat([Body, Dash, Boundary_bytes, Dash, Crlf]).
-file("src/multipartkit/encoder.gleam", 103).
-spec build_stream_header_block(multipartkit@stream:stream_part()) -> bitstring().
build_stream_header_block(Stream_part) ->
build_header_block(erlang:element(2, Stream_part)).
-file("src/multipartkit/encoder.gleam", 107).
-spec stop_after_first_error(
gleam@yielder:yielder({ok, bitstring()} |
{error, multipartkit@error:multipart_error()})
) -> gleam@yielder:yielder({ok, bitstring()} |
{error, multipartkit@error:multipart_error()}).
stop_after_first_error(Source) ->
gleam@yielder:transform(
Source,
false,
fun(Seen_error, Item) ->
gleam@bool:guard(
Seen_error,
done,
fun() -> {next, Item, gleam@result:is_error(Item)} end
)
end
).
-file("src/multipartkit/encoder.gleam", 74).
?DOC(
" Encode a stream of `StreamPart`s into a yielder of byte chunks.\n"
"\n"
" Errors propagated from a `StreamPart`'s body iterator are forwarded as\n"
" `Error(_)`. After the first error, the yielder is exhausted.\n"
).
-spec encode_stream(
binary(),
gleam@yielder:yielder(multipartkit@stream:stream_part())
) -> gleam@yielder:yielder({ok, bitstring()} |
{error, multipartkit@error:multipart_error()}).
encode_stream(Boundary, Parts) ->
Boundary_bytes = gleam_stdlib:identity(Boundary),
Body = gleam@yielder:flat_map(
Parts,
fun(The_part) ->
Header_block = build_stream_header_block(The_part),
Prefix = gleam@yielder:from_list(
[{ok, <<"--"/utf8>>},
{ok, Boundary_bytes},
{ok, <<"\r\n"/utf8>>},
{ok, Header_block},
{ok, <<"\r\n"/utf8>>}]
),
Suffix = gleam@yielder:from_list([{ok, <<"\r\n"/utf8>>}]),
gleam@yielder:append(
gleam@yielder:append(Prefix, erlang:element(6, The_part)),
Suffix
)
end
),
Closing = gleam@yielder:from_list(
[{ok, <<"--"/utf8>>},
{ok, Boundary_bytes},
{ok, <<"--"/utf8>>},
{ok, <<"\r\n"/utf8>>}]
),
stop_after_first_error(gleam@yielder:append(Body, Closing)).
-file("src/multipartkit/encoder.gleam", 118).
-spec boundary_char(integer()) -> binary().
boundary_char(Index) ->
case Index of
0 ->
<<"0"/utf8>>;
1 ->
<<"1"/utf8>>;
2 ->
<<"2"/utf8>>;
3 ->
<<"3"/utf8>>;
4 ->
<<"4"/utf8>>;
5 ->
<<"5"/utf8>>;
6 ->
<<"6"/utf8>>;
7 ->
<<"7"/utf8>>;
8 ->
<<"8"/utf8>>;
9 ->
<<"9"/utf8>>;
10 ->
<<"a"/utf8>>;
11 ->
<<"b"/utf8>>;
12 ->
<<"c"/utf8>>;
13 ->
<<"d"/utf8>>;
14 ->
<<"e"/utf8>>;
15 ->
<<"f"/utf8>>;
16 ->
<<"g"/utf8>>;
17 ->
<<"h"/utf8>>;
18 ->
<<"i"/utf8>>;
19 ->
<<"j"/utf8>>;
20 ->
<<"k"/utf8>>;
21 ->
<<"l"/utf8>>;
22 ->
<<"m"/utf8>>;
23 ->
<<"n"/utf8>>;
24 ->
<<"o"/utf8>>;
25 ->
<<"p"/utf8>>;
26 ->
<<"q"/utf8>>;
27 ->
<<"r"/utf8>>;
28 ->
<<"s"/utf8>>;
29 ->
<<"t"/utf8>>;
30 ->
<<"u"/utf8>>;
31 ->
<<"v"/utf8>>;
32 ->
<<"w"/utf8>>;
33 ->
<<"x"/utf8>>;
34 ->
<<"y"/utf8>>;
_ ->
<<"z"/utf8>>
end.
-file("src/multipartkit/encoder.gleam", 163).
-spec random_chars(integer(), binary()) -> binary().
random_chars(Remaining, Acc) ->
gleam@bool:guard(
Remaining =< 0,
Acc,
fun() ->
Char_index = gleam@int:random(36),
random_chars(
Remaining - 1,
<<Acc/binary, (boundary_char(Char_index))/binary>>
)
end
).
-file("src/multipartkit/encoder.gleam", 159).
-spec generate_boundary() -> binary().
generate_boundary() ->
<<"----multipartkit-"/utf8, (random_chars(24, <<""/utf8>>))/binary>>.
-file("src/multipartkit/encoder.gleam", 63).
?DOC(
" Encode a `Form` and return `#(content_type, body)`.\n"
"\n"
" `content_type` is the full value to set on the HTTP `Content-Type` header\n"
" — for example `multipart/form-data; boundary=----abc123`. The boundary is\n"
" generated freshly per call. Two calls on the same `Form` therefore\n"
" produce different `content_type` values.\n"
"\n"
" Note: v0.1.0 uses `gleam/int.random` for boundary character generation.\n"
" This is sufficient for collision avoidance with normal payloads but is\n"
" not cryptographically strong; do not rely on the boundary for security\n"
" invariants.\n"
).
-spec encode_form(multipartkit@form:form()) -> {binary(), bitstring()}.
encode_form(The_form) ->
Boundary = generate_boundary(),
Body = encode(Boundary, multipartkit@form:parts(The_form)),
Content_type = <<"multipart/form-data; boundary="/utf8, Boundary/binary>>,
{Content_type, Body}.