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:

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. at is the zero-based entry index; raw is 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 valid type/subtype per 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 concrete type/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 than q and accept-ext, which are carried on AcceptItem).
  • TypeWildcard(type_) matches any subtype within a top-level type (e.g. image/*). The carried type_ is already lowercased.
  • AnyType matches any media range (*/*).
pub type MediaRange {
  Specific(mimetype.MimeType)
  TypeWildcard(type_: String)
  AnyType
}

Constructors

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 Accept header could not be parsed. Carries the underlying AcceptError so callers routing to a 400 Bad Request can 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.

  • NoOverlap

    All 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 to 406 Not Acceptable.

One parsed entry from Accept-Encoding, Accept-Charset, or Accept-Language. The value is lowercased.

pub type ValueItem {
  ValueItem(value: String, q: Float)
}

Constructors

  • ValueItem(value: String, q: Float)

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

  1. q-value descending,
  2. specificity descending (concrete > type/* > */*),
  3. accept-ext count descending (RFC 9110 §12.5.1 tie-breaker).
pub fn prefer_values(items: List(ValueItem)) -> List(ValueItem)

Stable-sort ValueItems by q-value descending.

Search Document