content-negotiation
Content negotiation and API mode
MIME types
MVC::Keayl::Mime is the format registry. mime-type maps a format to its MIME
type and mime-format maps a type (ignoring parameters and aliases) back to a
format. register-mime adds or extends entries:
mime-type('json'); # application/json
mime-format('application/json; q=1'); # json
register-mime('msgpack', 'application/msgpack');parse-accept orders an Accept header's types by quality, and negotiate
chooses the best of a list of available formats, honouring */* and type/*
wildcards and falling back to the first available format when there is no
preference:
negotiate(['html', 'json'], 'application/json;q=0.4, text/html;q=0.8'); # htmlrespond-to
A controller action serves several formats with respond-to, taking an ordered
list of format-to-block pairs. The format is taken from the path extension if
present, otherwise negotiated from the Accept header; the first declared format
is the default. A request that matches none gets 406:
method show {
self.respond-to([
html => { self.render('show') },
json => { self.render(:json($post.to-hash)) },
]);
}/posts.json forces JSON; /posts with Accept: text/html serves HTML.
Variants
A variant selects a device-specific view of the same format. request.variant
holds the variant, set explicitly or from a user-agent heuristic:
self.request.set-variant('phone');
self.request.set-variant(self.request.detect-variant); # phone, tablet, or noneA format block in respond-to may be a map of variant names to blocks instead of
a single block. The block for the current variant runs, falling back to any:
self.respond-to([
html => {
phone => { self.render('show_phone') },
any => { self.render('show') },
},
]);Variants also steer template lookup. When the variant is set, the renderer
prefers a variant template before the plain one, so phone resolves
show.html+phone.haml and falls back to show.html.haml.
API controllers
MVC::Keayl::APIController is a JSON-first controller. It needs no view renderer:
rendering a bare object or a collection produces JSON, while string templates and
explicit render options still work as on a normal controller.
class PostsController is MVC::Keayl::APIController {
method show { self.render($post) } # {"title":"..."}
method index { self.render($posts) } # [{"title":"..."}, ...]
method made { self.render($post, :status(201)) }
}A model is serialized through serialize, which calls the object's to-hash
(recursing into collections) by default. Provide a serializer to control the
shape:
PostsController.new(serializer => -> $model { %( id => $model.id, title => $model.title ) });