-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.