NAME

PAGI::Request::Negotiate - Content negotiation utilities for PAGI

SYNOPSIS

use PAGI::Request::Negotiate;

# Parse Accept header
my @types = PAGI::Request::Negotiate->parse_accept(
    'text/html, application/json;q=0.9, */*;q=0.1'
);
# Returns: (['text/html', 1], ['application/json', 0.9], ['*/*', 0.1])

# Find best match
my $best = PAGI::Request::Negotiate->best_match(
    ['application/json', 'text/html'],
    'text/html, application/json;q=0.9'
);
# Returns: 'text/html'

# Check if type is acceptable
my $accepts = PAGI::Request::Negotiate->accepts_type(
    'text/html, application/json',
    'json'
);

# Get quality value for type
my $quality = PAGI::Request::Negotiate->quality_for_type(
    'application/json;q=0.9',
    'json'
);

DESCRIPTION

PAGI::Request::Negotiate provides utilities for HTTP content negotiation, including parsing Accept headers and finding the best matching content type.

This module supports quality values, wildcards (*/*, type/*), and common MIME type shortcuts (json, html, xml, etc.).

CLASS METHODS

parse_accept

my @types = PAGI::Request::Negotiate->parse_accept($header);

Parse an Accept header and return a list of arrayrefs containing [media_type, quality] sorted by preference (quality descending, then specificity descending).

If no Accept header is provided, returns a single entry for */* with quality 1.

Quality values are clamped to the range [0, 1].

Specificity ordering: exact types > type/* > */*

best_match

my $type = PAGI::Request::Negotiate->best_match(\@supported, $accept_header);

Find the best matching content type from @supported based on the Accept header. Returns the best match or undef if none acceptable.

@supported can contain full MIME types or shortcuts (html, json, xml, etc.)

The returned value is from the @supported array (preserves shortcuts).

type_matches

my $bool = PAGI::Request::Negotiate->type_matches($type, $pattern);

Check if a media type matches a pattern. Patterns can include wildcards like */* or text/*.

Both type and pattern are compared case-insensitively.

normalize_type

my $mime = PAGI::Request::Negotiate->normalize_type($type);

Convert a type shortcut to its full MIME type. Known shortcuts include:

html   => text/html
json   => application/json
xml    => application/xml
atom   => application/atom+xml
rss    => application/rss+xml
text   => text/plain
txt    => text/plain
css    => text/css
js     => application/javascript
png    => image/png
jpg    => image/jpeg
jpeg   => image/jpeg
gif    => image/gif
svg    => image/svg+xml
pdf    => application/pdf
zip    => application/zip
form   => application/x-www-form-urlencoded

If the type is already a MIME type (contains '/'), it's returned as-is.

Unknown shortcuts are prefixed with 'application/'.

accepts_type

my $bool = PAGI::Request::Negotiate->accepts_type($accept_header, $type);

Check if a specific content type is acceptable based on the Accept header.

The type can be a full MIME type or a shortcut.

Returns false if the type has quality=0 (explicitly rejected).

quality_for_type

my $q = PAGI::Request::Negotiate->quality_for_type($accept_header, $type);

Get the quality value for a specific type. Returns 0 if not acceptable.

When multiple patterns match (e.g., both text/* and text/html), returns the quality of the most specific match.

The type can be a full MIME type or a shortcut.

EXAMPLES

Content Negotiation in a PAGI App

use PAGI::Request::Negotiate;

async sub app ($scope, $receive, $send) {
    my $accept = $scope->{headers}{accept} // '*/*';

    my $format = PAGI::Request::Negotiate->best_match(
        ['json', 'html', 'xml'],
        $accept
    );

    my ($body, $content_type);
    if ($format eq 'json') {
        $body = '{"message":"Hello"}';
        $content_type = 'application/json';
    } elsif ($format eq 'html') {
        $body = '<h1>Hello</h1>';
        $content_type = 'text/html';
    } else {
        $body = '<message>Hello</message>';
        $content_type = 'application/xml';
    }

    await $send->({
        type => 'http.response.start',
        status => 200,
        headers => [['content-type', $content_type]],
    });

    await $send->({
        type => 'http.response.body',
        body => $body,
    });
}

SEE ALSO

PAGI::Request, PAGI

RFC 7231 Section 5.3 - Content Negotiation

AUTHOR

PAGI Contributors