Current section
Files
Jump to
Current section
Files
src/contenty.erl
-module(contenty).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/contenty.gleam").
-export([mime/1, mime_type/1, mime_subtype/1, params/1, param/2, parse/1, new/3, to_string/1]).
-export_type([content_type/0, content_type_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.
-opaque content_type() :: {content_type,
binary(),
binary(),
gleam@dict:dict(binary(), binary())}.
-type content_type_error() :: invalid_content_type.
-file("src/contenty.gleam", 33).
?DOC(" Returns the full MIME string, e.g. `\"text/html\"`.\n").
-spec mime(content_type()) -> binary().
mime(Content_type) ->
<<<<(erlang:element(2, Content_type))/binary, "/"/utf8>>/binary,
(erlang:element(3, Content_type))/binary>>.
-file("src/contenty.gleam", 39).
?DOC(" Returns the MIME type (the part before the slash), e.g. `\"text\"`.\n").
-spec mime_type(content_type()) -> binary().
mime_type(Content_type) ->
erlang:element(2, Content_type).
-file("src/contenty.gleam", 45).
?DOC(" Returns the MIME subtype (the part after the slash), e.g. `\"html\"`.\n").
-spec mime_subtype(content_type()) -> binary().
mime_subtype(Content_type) ->
erlang:element(3, Content_type).
-file("src/contenty.gleam", 51).
?DOC(" Returns all parameters from the content type.\n").
-spec params(content_type()) -> gleam@dict:dict(binary(), binary()).
params(Content_type) ->
erlang:element(4, Content_type).
-file("src/contenty.gleam", 57).
?DOC(" Returns the value of a parameter by key (case-insensitive).\n").
-spec param(content_type(), binary()) -> {ok, binary()} | {error, nil}.
param(Content_type, Key) ->
gleam_stdlib:map_get(erlang:element(4, Content_type), string:lowercase(Key)).
-file("src/contenty.gleam", 127).
-spec parse_param(binary()) -> {ok, {binary(), binary()}} | {error, nil}.
parse_param(Param) ->
gleam@result:map(
gleam@string:split_once(Param, <<"="/utf8>>),
fun(_use0) ->
{Key, Value} = _use0,
{begin
_pipe = gleam@string:trim_start(Key),
string:lowercase(_pipe)
end,
gleam@string:trim_end(Value)}
end
).
-file("src/contenty.gleam", 120).
-spec parse_params(list(binary())) -> gleam@dict:dict(binary(), binary()).
parse_params(Params) ->
_pipe = gleam@list:map(Params, fun parse_param/1),
_pipe@1 = gleam@list:filter_map(_pipe, fun gleam@function:identity/1),
_pipe@2 = gleam@list:filter(
_pipe@1,
fun(S) -> not gleam@string:is_empty(erlang:element(1, S)) end
),
maps:from_list(_pipe@2).
-file("src/contenty.gleam", 136).
-spec assert_not_empty(binary()) -> {ok, binary()} |
{error, content_type_error()}.
assert_not_empty(Value) ->
case Value of
<<""/utf8>> ->
{error, invalid_content_type};
_ ->
{ok, Value}
end.
-file("src/contenty.gleam", 105).
-spec parse_mime(binary()) -> {ok, {binary(), binary()}} |
{error, content_type_error()}.
parse_mime(Mime) ->
case gleam@string:split(Mime, <<"/"/utf8>>) of
[Mime_type, Mime_subtype] ->
Mime_type@1 = begin
_pipe = gleam@string:trim_start(Mime_type),
_pipe@1 = string:lowercase(_pipe),
assert_not_empty(_pipe@1)
end,
gleam@result:'try'(
Mime_type@1,
fun(Mime_type@2) ->
Mime_subtype@1 = begin
_pipe@2 = gleam@string:trim_end(Mime_subtype),
_pipe@3 = string:lowercase(_pipe@2),
assert_not_empty(_pipe@3)
end,
gleam@result:map(
Mime_subtype@1,
fun(Mime_subtype@2) -> {Mime_type@2, Mime_subtype@2} end
)
end
);
_ ->
{error, invalid_content_type}
end.
-file("src/contenty.gleam", 91).
?DOC(
" Parses an HTTP `Content-Type` header into a structured ContentType value.\n"
"\n"
" Parameters are:\n"
" - Lowercased for keys (`charset`, `boundary`, etc.)\n"
" - Trimmed of extra whitespace\n"
" - Parsed safely — malformed entries like `\"foo\"` (without `=`) are ignored\n"
"\n"
" # Examples\n"
"\n"
" ```gleam\n"
" import contenty\n"
"\n"
" let content_type_1 = contenty.parse(\"text/html; charset=utf-8\")\n"
" // Ok(ContentType(\"text\", \"html\", dict.from_list([#(\"charset\", \"utf-8\")])))\n"
"\n"
" let content_type_2 = contenty.parse(\"application/json\")\n"
" // Ok(ContentType(\"application\", \"json\", dict.new()))\n"
"\n"
" let content_type_3 = contenty.parse(\"\")\n"
" // Error(InvalidContentType)\n"
" ```\n"
"\n"
" # See also\n"
" - [RFC 2045 §5.1: Content-Type Header Field](https://www.rfc-editor.org/rfc/rfc2045)\n"
" - [IANA Media Types Registry](https://www.iana.org/assignments/media-types/media-types.xhtml)\n"
).
-spec parse(binary()) -> {ok, content_type()} | {error, content_type_error()}.
parse(Content_type) ->
Parts = begin
_pipe = gleam@string:split(Content_type, <<";"/utf8>>),
gleam@list:map(_pipe, fun gleam@string:trim/1)
end,
case Parts of
[] ->
{error, invalid_content_type};
[Mime | Params] ->
gleam@result:map(
parse_mime(Mime),
fun(_use0) ->
{Mime_type, Mime_subtype} = _use0,
{content_type,
Mime_type,
Mime_subtype,
parse_params(Params)}
end
)
end.
-file("src/contenty.gleam", 172).
?DOC(
" Creates a new `ContentType` value from its components.\n"
"\n"
" This function normalises and validates the input:\n"
" - `mime_type` and `mime_subtype` are lowercased and trimmed\n"
" - `params` keys are lowercased and trimmed\n"
" - `params` values are trimmed (but not lowercased)\n"
"\n"
" Returns `Error(ContentTypeError)` if either the type or subtype\n"
" is empty or malformed.\n"
"\n"
" # Examples\n"
"\n"
" ```gleam\n"
" import contenty\n"
" import gleam/dict\n"
"\n"
" let assert Ok(content_type) = contenty.new(\n"
" \"Text\",\n"
" \"HTML\",\n"
" dict.from_list([#(\"Charset\", \"utf-8\")]),\n"
" )\n"
"\n"
" contenty.mime(content_type)\n"
" // \"text/html\"\n"
"\n"
" contenty.params(content_type)\n"
" // dict.from_list([#(\"charset\", \"utf-8\")])\n"
" ```\n"
).
-spec new(binary(), binary(), gleam@dict:dict(binary(), binary())) -> {ok,
content_type()} |
{error, content_type_error()}.
new(Mime_type, Mime_subtype, Params) ->
gleam@result:map(
parse_mime(
<<<<Mime_type/binary, "/"/utf8>>/binary, Mime_subtype/binary>>
),
fun(_use0) ->
{Mime_type@1, Mime_subtype@1} = _use0,
Params@1 = begin
_pipe = maps:to_list(Params),
_pipe@2 = gleam@list:map(
_pipe,
fun(Entry) ->
Key = begin
_pipe@1 = gleam@string:trim_start(
erlang:element(1, Entry)
),
string:lowercase(_pipe@1)
end,
Value = gleam@string:trim_end(erlang:element(2, Entry)),
{Key, Value}
end
),
maps:from_list(_pipe@2)
end,
{content_type, Mime_type@1, Mime_subtype@1, Params@1}
end
).
-file("src/contenty.gleam", 221).
-spec params_to_string(gleam@dict:dict(binary(), binary())) -> binary().
params_to_string(Params) ->
_pipe = maps:to_list(Params),
_pipe@1 = gleam@list:map(
_pipe,
fun(Entry) ->
<<<<<<" "/utf8, (erlang:element(1, Entry))/binary>>/binary,
"="/utf8>>/binary,
(erlang:element(2, Entry))/binary>>
end
),
gleam@string:join(_pipe@1, <<";"/utf8>>).
-file("src/contenty.gleam", 217).
?DOC(
" Converts a `ContentType` back to its string form.\n"
"\n"
" Produces a canonicalised header string in the form:\n"
"\n"
" ```text\n"
" type/subtype; key=value; key2=value2\n"
" ```\n"
"\n"
" Parameters are joined with `;` and appear in insertion order.\n"
"\n"
" # Examples\n"
"\n"
" ```gleam\n"
" import contenty\n"
" import gleam/dict\n"
"\n"
" let assert Ok(content_type) = contenty.new(\n"
" \"text\",\n"
" \"html\",\n"
" dict.from_list([#(\"charset\", \"utf-8\")]),\n"
" )\n"
"\n"
" contenty.to_string(content_type)\n"
" // \"text/html; charset=utf-8\"\n"
" ```\n"
).
-spec to_string(content_type()) -> binary().
to_string(Content_type) ->
<<<<(mime(Content_type))/binary, ";"/utf8>>/binary,
(params_to_string(erlang:element(4, Content_type)))/binary>>.