Hcs.Plug.Negotiate

hcs · API reference

Content negotiation plug.

Parses Accept headers and selects response format based on client preferences and server capabilities. Follows RFC 7231 content negotiation semantics.

Usage

(* In router pipeline *)
let pipeline =
  Plug.Negotiate.create ~formats:[ Json; Html ] () @> Plug.identity

(* In handler *)
let handler req =
  match get_format req with
  | Some Json -> Response.json {|{"message": "hello"}|}
  | Some Html -> Response.html "<h1>Hello</h1>"
  | _ -> Response.text "hello"

(* Or use respond helper *)
let handler req =
  respond req
    ~json:(fun () -> {|{"message": "hello"}|})
    ~html:(fun () -> "<h1>Hello</h1>")

Types

type media_type = {
  type_ : string; (* e.g., "application" *)
  subtype : string; (* e.g., "json" *)
  quality : float; (* 0.0 - 1.0, default 1.0 *)
  params : (string * string) list; (* Additional parameters *)
}

Parsed media type from Accept header

type format = 
  | Json
  | Html
  | Text
  | Xml
  | Csv
  | Custom of string (* Custom MIME type *)

Response format - common formats plus custom

type error = 
  | Not_acceptable

Content negotiation errors

exception Not_acceptable_exn

Format Utilities

val mime_type_of_format : format -> string

Get MIME type string for a format

val format_of_mime_type : string -> format option

Get format from MIME type string

val format_name : format -> string

Get short name for format (for internal use)

Accept Header Parsing

val skip_ws : string -> int -> int

Skip whitespace in string starting at position i

val parse_token : string -> int -> string * int

Parse a token (sequence of non-separator chars)

val parse_quoted : string -> int -> string * int

Parse a quoted string

val parse_value : string -> int -> string * int

Parse a parameter value (token or quoted-string)

val parse_params : string -> int -> (string * string) list * float * int

Parse parameters after media type: ;name=value pairs

val parse_media_type : string -> int -> (media_type * int) option

Parse a single media type entry

val parse_accept : string -> media_type list

Parse Accept header into list of media types, sorted by quality (highest first).

Handles:

  • Multiple types separated by commas
  • Quality values (q=0.8)
  • Parameters (charset=utf-8)
  • Wildcards (* / * and type/ *) parameter header Accept header value returns List of media types sorted by quality descending

Negotiation

val media_type_matches : media_type -> format -> bool

Check if a media type matches a format

val negotiate : accept:string -> available:format list -> format option

Negotiate best format from Accept header.

parameter accept Accept header value parameter available List of formats the server can produce returns Best matching format or None if no match

val negotiate_exn : accept:string -> available:format list -> format

Negotiate best format, raising exception if no match.

parameter accept Accept header value parameter available List of formats the server can produce returns Best matching format raises Not_acceptable_exn if no format matches

Request Format Storage

val format_header : string

Key for storing negotiated format in request. We store format as a header since requests are immutable records.

val get_format : Server.request -> format option

Get negotiated format from request (set by Negotiate plug)

val set_format : Server.request -> format -> Server.request

Set negotiated format on request

Response Helpers

val respond_format : format -> body:string -> Server.response

Create response with correct Content-Type for format

val respond : 
  Server.request ->
  ?json:(unit -> string) ->
  ?html:(unit -> string) ->
  ?text:(unit -> string) ->
  ?xml:(unit -> string) ->
  unit ->
  Server.response

Respond based on negotiated format with lazy body generation. Only evaluates the body function for the selected format.

parameter req Request with negotiated format parameter json Function to generate JSON body parameter html Function to generate HTML body parameter text Optional function for plain text (defaults to json) parameter xml Optional function for XML returns Response with appropriate body and Content-Type

Plug

val create : 
  formats:format list ->
  unit ->
  (Server.request -> Server.response) ->
  Server.request ->
  Server.response

Create content negotiation plug.

Parses the Accept header and determines the best response format. If no acceptable format is found, returns 406 Not Acceptable.

parameter formats List of formats this endpoint can produce returns Plug that sets negotiated format on request