mimetype/accept
RFC 9110 §12.5 content negotiation: parser and selector for the
Accept, Accept-Encoding, Accept-Charset, and Accept-Language
header families.
The module exposes:
parse/1— turn a rawAcceptheader into a list ofAcceptItemvalues, preserving the client’s q-values and any accept-ext parameters (RFC 9110 §12.5.1).prefer/1— stable-sort parsed items by(q desc, specificity desc, accept-ext count desc)so callers can iterate in the order the client most prefers.negotiate/2— given a parsed client header and a list of server offers, return the bestMimeTypeto serve, orNonewhen no offer is acceptable. Implements the §12.5.1 proactive-negotiation algorithm with the documented essence-only matching restriction.parse_encoding/1,parse_charset/1,parse_language/1and their companionprefer_values/1/negotiate_value/2.
Types
Why a header could not be parsed.
pub type AcceptError {
Malformed(at: Int, raw: String)
InvalidQValue(raw: String)
InvalidMediaRange(raw: String)
}
Constructors
-
Malformed(at: Int, raw: String)An entry did not match the expected
media-range[;params]shape.atis the zero-based entry index;rawis the raw entry text. -
InvalidQValue(raw: String)An entry’s q-value was syntactically invalid.
-
InvalidMediaRange(raw: String)A media-range field was not a wildcard, a
type/*form, or a validtype/subtypeper RFC 6838.
One parsed entry from an Accept header. q defaults to 1.0 if
the wire form omits it; extensions holds the accept-ext name/value
pairs that appeared after the q= parameter, with names
lowercased and values preserved.
pub type AcceptItem {
AcceptItem(
range: MediaRange,
q: Float,
extensions: List(#(String, String)),
)
}
Constructors
-
AcceptItem( range: MediaRange, q: Float, extensions: List(#(String, String)), )
A single media-range entry as it appears on the wire.
Specific(mt)matches a concretetype/subtype. Parameter-level “more-specific” matching per RFC 9110 §12.5.1 is out of scope: matching looks at the essence only and ignores attached media-range parameters (other thanqand accept-ext, which are carried onAcceptItem).TypeWildcard(type_)matches any subtype within a top-level type (e.g.image/*). The carriedtype_is already lowercased.AnyTypematches any media range (*/*).
pub type MediaRange {
Specific(mimetype.MimeType)
TypeWildcard(type_: String)
AnyType
}
Constructors
-
Specific(mimetype.MimeType) -
TypeWildcard(type_: String) -
AnyType
Reasons negotiate_strings / negotiate_encoding_strings /
negotiate_charset_strings / negotiate_language_strings can
return without a selection.
pub type NegotiateError {
InvalidHeader(reason: AcceptError)
InvalidOffer(raw: String)
NoOverlap
}
Constructors
-
InvalidHeader(reason: AcceptError)The
Acceptheader could not be parsed. Carries the underlyingAcceptErrorso callers routing to a400 Bad Requestcan surface the failing offset / shape verbatim. -
InvalidOffer(raw: String)An offer string was not a valid
type/subtype. Carries the rejected offer so the caller knows which entry to fix. -
NoOverlapAll entries parsed cleanly but no offer was acceptable to the client (e.g. every match had
q=0, or the offer list was empty). Maps to406 Not Acceptable.
Values
pub fn negotiate(
client_accepts client: List(AcceptItem),
server_offers offers: List(mimetype.MimeType),
) -> option.Option(mimetype.MimeType)
Pick the best server offer for a parsed Accept header. Returns
None when no offer is acceptable (e.g. every match has q=0, or
server_offers is empty).
Special case: if every client item is AnyType with q>0, the
server’s first offer wins (server preference). Otherwise we score
each server offer by the best matching client item — (q, specificity, ext_count) — and break ties by the order the offer
appears in server_offers.
Matching is essence-only: parameter-level “more-specific” matching per RFC 9110 §12.5.1 is out of scope.
pub fn negotiate_charset_strings(
header header: String,
offers offers: List(String),
) -> Result(String, NegotiateError)
Negotiate against a raw Accept-Charset header and a list of
charset offer strings.
pub fn negotiate_encoding_strings(
header header: String,
offers offers: List(String),
) -> Result(String, NegotiateError)
Negotiate against a raw Accept-Encoding header and a list of
encoding offer strings. Returns the selected encoding verbatim
from offers.
pub fn negotiate_language_strings(
header header: String,
offers offers: List(String),
) -> Result(String, NegotiateError)
Negotiate against a raw Accept-Language header and a list of
language tag offer strings.
pub fn negotiate_strings(
header header: String,
offers offers: List(String),
) -> Result(String, NegotiateError)
Negotiate against a raw Accept header and a list of server
offer strings.
Returns the selected offer verbatim from offers (not the
parsed-and-rendered MimeType), so callers can compare the result
against their own routing table without re-parsing.
Error(InvalidHeader(_)) lets the caller route a malformed client
header to 400; Error(InvalidOffer(_)) lets it route a buggy
server configuration to 500; Error(NoOverlap) maps cleanly to
406 Not Acceptable. The previous three-step ceremony
(accept.parse → mimetype.parse × n → negotiate → render)
collapses into a single call.
pub fn negotiate_value(
client_accepts client: List(ValueItem),
server_offers offers: List(String),
) -> option.Option(String)
Pick the best server offer for an Accept-Encoding /
Accept-Charset / Accept-Language header. The * wildcard
matches any value not already named explicitly; entries with q=0
are excluded.
pub fn parse(
header: String,
) -> Result(List(AcceptItem), AcceptError)
Parse an Accept header into a list of AcceptItem values in the
order they appeared on the wire. Use prefer/1 afterwards to sort
by preference.
Whitespace tolerance: empty entries ("a, , b") and surrounding
whitespace on each entry / parameter are accepted per RFC 9110
§5.6.3.
pub fn parse_charset(
header: String,
) -> Result(List(ValueItem), AcceptError)
Parse an Accept-Charset header.
pub fn parse_encoding(
header: String,
) -> Result(List(ValueItem), AcceptError)
Parse an Accept-Encoding header.
pub fn parse_language(
header: String,
) -> Result(List(ValueItem), AcceptError)
Parse an Accept-Language header.
pub fn prefer(items: List(AcceptItem)) -> List(AcceptItem)
Stable-sort parsed AcceptItems by client preference:
- q-value descending,
- specificity descending (concrete >
type/*>*/*), - accept-ext count descending (RFC 9110 §12.5.1 tie-breaker).