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