Packages

A tiny Gleam library for parsing and inspecting HTTP Content-Type headers.

Current section

Files

Jump to
contenty src contenty.erl
Raw

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]).
-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_start(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.